Freigeben über


A Generic Constraint Question

Here's a question I get fairly frequently: the user desires to create a generic type Bar<T> constrained such that T is guaranteed to be a Foo<U>, for some U.

The way they usually try to write this is

class Bar<T> where T : Foo<T> {...

which of course then does not work. The user discovers this when they type

new Bar<Foo<string>>()

and get an error stating that the constraint has been violated. Which it certainly has. The constraint says that T must be Foo<T>, so therefore string must be Foo<string>, which clearly it is not.

There are a few ways to solve this problem. One is to simply introduce a new type parameter:

class Bar<T, U> where T : Foo<U>

this means that you now have some redundancy:

new Bar<Foo<string>, string>()

But let's stop and think for a moment about what the user desired in the first place. Suppose there were a way to specify what we sometimes call a "mumble type" on the C# design team:

class Bar<T> where T : Foo<?> {...

Think about the consequences of that on the caller side; the compiler could verify that new Bar<Foo<string>>() was legal. But what could the compiler verify on the callee side?

class Bar<T> where T : Foo<?> {
public void Blah(T t) {
t.

t dot what? We don't know anything about T except that it is Foo<something>. What methods, properties, fields or events of Foo could we access? Only those that in no way consume the generic type, which seems like precious little. If Foo had a whole lot of stuff that wasn't generic, why was it made generic in the first place?

But this then leads to an insight; if the user owns the Foo<T> class, then they could do precisely that:

public abstract class FooBase
{
private FooBase() {} // Not inheritable by anyone else
public class Foo<U> : FooBase {...generic stuff ...}
... nongeneric stuff ...
}

public class Bar<T> where T: FooBase { ... }
...
new Bar<FooBase.Foo<string>>()

and now everyone is happy. The compiler restricts the construction of Bar to FooBase on the caller side. The compiler ensures that every runtime instance of FooBase is an instance of Foo<T>. And the compiler allows the callee side to use all the non-generic methods on FooBase.

Personally, I would go for the first solution myself. But it's edifying to try and find additional solutions, even if they are a bit odd.

Comments

  • Anonymous
    May 19, 2008
    The comment has been removed

  • Anonymous
    May 19, 2008
    The comment has been removed

  • Anonymous
    May 19, 2008
    The comment has been removed

  • Anonymous
    May 19, 2008
    The comment has been removed

  • Anonymous
    May 19, 2008
    The comment has been removed

  • Anonymous
    May 19, 2008
    A slightly related, but still unrelated question: Why are there no typedefs in C#. Sometimes it would be useful to  say something like class X<T> {  class Y<T> {  }  internal typedef Dictionary<T, IEnumerable<Y<T>>> MySensibleName; } I realize public and protected typedefs would be hard since all callers would need to be recompiled if the typedef was changed, but what would be the problem with internal and private ones?

  • Anonymous
    May 19, 2008
    I hate Java's generics with a passion (type-erasure is horrible) but I really do like the wildcard type constraint stuff as Andrew Cook demonstrated.  I also like the call-site variance. E rik: you can use the using statement to alias things. using Dictionary<T, IEnumerable<Y<T>>>  = MySensibleName It is unfortunate that it uses the same using keyword.

  • Anonymous
    May 19, 2008
    Andrew Cook: Actually, what you are describing there is how Java does contravariance/covariance. See my blog archive on this subject for how we are considering doing this in a future version of C#. Mark: Call site variance is interesting, but kind of strange. We are probably not going to support it in C#. Mark, Erik:  You can use "using" as a form of typedef, but not in a generic manner.  That is, you cannot say: using Frib.Frab.Blah.Abc.Def<T> = MyDef<T> to define a "family" of typedefs. Though I agree that it would be useful, unfortunately this did not make the short list of features that we are going to do for the next version of C#.

  • Anonymous
    May 19, 2008
    I do agree that using is possible, but it only works in one file. Sometimes in those cases, I derive a new class internal class MySensibleName : Dictionary<T, IEnumerable<Y<T>> {} Does anyone have a better idea?

  • Anonymous
    May 19, 2008
    Generally when I've found myself in the situation of having a generic type for implementation reasons but callers aren't interested in the type parameter(s), I use a non-generic or less generic interface rather than base class to abstract away the type parameter(s) the caller doesn't care about, so IFoo instead of FooBase. As for mumble types and typedefs, I find where I want the abbreviation is not in defining the generic type but in using it. So for example if I have T<U,V> where U : IComparer<V>, I hate having to write new T<MyStringComparer, string>. It's like preschool programming: the compiler says "So we know that MyStringComparer implements IComparer<string>, so that means V must be.......?" and we have to shout out "string!" and the compiler says "Very good! Now let's all write string right there. Has everybody done that now?" I would rather be able to write something like T<U, infer V> where U : IComparer<V> or to have public interface IComparer<T> { typedef T ComparandType; ... } and then T<U, ?> where U : IComparer<?> and then refer to ? as U.ComparandType. In either case, even though the CLR would still see T2, the compiler would apply the syntactic sugar when I write T&lt;MyStringComparer&gt; and emit T2[MyStringComparer, System.String].

  • Anonymous
    May 19, 2008
    I find I am frequently defining empty classes and interfaces to give a name to the most frequently used specializations of generic types. This is where I would love a public typedef. It would just be a little metadata that creates a public alias. To the consuming assembly it would look just like a type and the runtime would treat it the same way the C++ compiler treats typedefs.

  • Anonymous
    May 19, 2008
    @MKane, I also would like to be able to do away with "preschool programming". In the business layer of one of my applications I have a large set of classes with two generic parameters where one of the types is inferable but the compiler requires it to be written out anyway. I also would like to see at least internal typedefs. I don't need them all that often, but I do find myself missing them occasionally.

  • Anonymous
    May 19, 2008
    Eric Lippert vient de publier un excellent post sur les génériques . L'idée est de pouvoir faire ceci

  • Anonymous
    May 19, 2008
    > Generally when I've found myself in the situation of having a generic type for implementation reasons but callers aren't interested in the type parameter(s), I use a non-generic or less generic interface rather than base class to abstract away the type parameter(s) the caller doesn't care about, so IFoo instead of FooBase. One thing that used to come up frequently on the newsgroup (when I still had time to read) was people trying to find some declaration which captured the spirit of void Baz(List<anything> list) We would lead them socratically to the realisation that the type that expressed the idea 'what behaviour is common to all List<anything>s' best is IList.

  • Anonymous
    May 19, 2008
    You have typedef in C# it's called 'type alias' : try this: using System; using IntList = System.Collections.Generic.List<int>; class Program { static void Main(string[] args) {        IntList list = new IntList();        list.Add(3);        Console.WriteLine(list[0]); } }

  • Anonymous
    May 20, 2008
    Typical, I've never needed to do this and then it came up twice today!

  • Anonymous
    May 20, 2008
    I think the FooBase solution should be the one everyone follows. Same thing happens in C++ std lib, basic_ios (a template) inherits from ios_base (non-template) which contains all the stuff which is agonistic to templates.

  • Anonymous
    May 21, 2008
    The comment has been removed

  • Anonymous
    May 21, 2008
    I'm having exactly this problem -- anyone figure out how I might do this...? Maybe<T> is a class which may or may not contain a value. Think of Nullable<T> that can contain reference types, or like the option monad. T might be a list, say List<U>. If it is, then we've got a type like "This object might contain a list of U's": the type should match "Maybe<T> where T:List<U>" I'd like to create an extension method called .ToList(), which gives me the list if there is one, or the empty list otherwise. So I'm trying the code below, and it compiles, but the method isn't showing up on autocomplete;    public static class OptionMonad    {        public static List<U> ToList<T, U>(this Maybe<T> maybeList) where T : List<U>        {            return (maybeList.Success ? maybeList.Value : new List<U>());        }    } Any ideas why this doesn't show up on autocomplete?

  • Anonymous
    May 21, 2008
    I just tried your code on my machine and I get intellisense just fine for this extension method. Can you give me a small, detailed, specific repro scenario?  Feel free to email it to me, Eric@Lippert.com.

  • Anonymous
    May 21, 2008
    The comment has been removed

  • Anonymous
    May 22, 2008
    Rather than place the links to the most recent C# team content directly in Community Convergence, I have

  • Anonymous
    June 15, 2008
    let's have Foo<T> like this:        class Foo<T>        {            public static void StaticMethod(T Value)            {                Console.WriteLine(Value);            }            public void Method(T Value)            {                Console.WriteLine(Value);            }        } Why than instead of for example this:        class Bar<T, U> where T : Foo<U>        {            public static void InvokeMethodsOnFoo(T foo, U value)            {                foo.Method(value);                Foo<U>.StaticMethod(value);            }        } make Bar like this:        class Bar<U>        {            public static void InvokeMethodsOnFoo(Foo<U> foo, U value)            {                foo.Method(value);                Foo<U>.StaticMethod(value);            }        } with only one generic parameter (of course it requires change in all Bar's methods).