แชร์ผ่าน


Design Guidelines: Provide type inference friendly Create function for generic objects

Really this guideline is a bit longer but putting it all in a blog title seemed a bit too much.  The full guideline should read: "If a generic class constructor arguments contain types of all generic parameters, provide a static method named Create on a static class of the same class name as the generic class which takes the same arguments and calls the constructor."  Quite a mouth full. 

Lets look at a specific example with Tuples.  Tuples are generic with respect to the values they are representing.  Without any type inference help we would have to write the following code to create a simple tuple.

             var tuple = new Tuple<int, string>(5, "astring");

Not too bad because we are using simple types.  But what happens when we are using really long type names? 

             var tuple2 = new Tuple<string,Dictionary<string, List<int>>>(val1, val2);

As we can see, the code is getting quite a bit uglier.  This pattern is in fact not maintainable once we start using un-namable types such as anonymous types or generics of anonymous types. 

The problem here is we are not leveraging the compilers type inference capabilities.  The compiler can easily infer the types of a tuple argument and hence create a tuple.  We just need to provide a mechanism to do so.  The best way is to define a static method on a static class with the same name as the generic.  Lets call this method Create.

     public static class Tuple {
        public static Tuple<TA, TB> Create<TA, TB>(TA valueA, TB valueB) { 
            return new Tuple<TA, TB>(valueA, valueB); 
        }
    }

The method Tuple.Create still has two generic parameters.  However since we are providing a set of values which contain types for every generic parameter, the compiler can infer the generic arguments.  Now we can create a Tuple without specifying any generic arguments.  Because no types are specified this will work with any value in the code including un-namable types. 

             var tuple = Tuple.Create(6, "astring");
            var tuple2 = Tuple.Create(6, new { name = "aname", value = 42 });

Comments

  • Anonymous
    April 11, 2008
    There are a few things I don't understand about this...
  1. What's so special about the static method of a static class that the compiler does type inference on it, but not on the constructor? The constructor has the same parameters; what information is the compiler missing that makes inference impossible in this case, while being possible in the other?
  2. I didn't even know you could declare a static class with the same name as a "normal" class. Are you sure that's what's going on, and it's not some kind of weird semi-partial thing where you can declare your class's static methods separately with "static" as a stand-in for "partial"?
  3. Why not just use a static method for the normal class? (Obviously related to the above.)

I mean, these are pretty obvious so I'm sure I'm not pointing out something new. Rather, why are my preconceptions about how things should work so wrong? Thanks!

  • Anonymous
    April 11, 2008
    The comment has been removed
  • Anonymous
    April 11, 2008
    Ah OK. I've definitely done reflection on generics; I think the use case that I was missing was when you have both generic and non-generic types with the same name (e.g. IEnumerable). In this case the behavior I was expecting was something like "if there is no non-generic IEnumerable, then let IEnumerable(x) do type inference on x and call into the constructor of IEnumerable<T>. But if there is, then it should just call the IEnumerable constructor. Unless x doesn't match any IEnumerable constructors, and does match the IEnumerable<T> type restriction---then use IEnumerable<T>." But obviously this is pretty bad in a few ways... lots of potential for introducing new code creating breaking changes etc. Thanks!