Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Partial generic type inference possible in C#?

Tags:

I am working on rewriting my fluent interface for my IoC class library, and when I refactored some code in order to share some common functionality through a base class, I hit upon a snag.

Note: This is something I want to do, not something I have to do. If I have to make do with a different syntax, I will, but if anyone has an idea on how to make my code compile the way I want it, it would be most welcome.

I want some extension methods to be available for a specific base-class, and these methods should be generic, with one generic type, related to an argument to the method, but the methods should also return a specific type related to the particular descendant they're invoked upon.

Better with a code example than the above description methinks.

Here's a simple and complete example of what doesn't work:

using System;  namespace ConsoleApplication16 {     public class ParameterizedRegistrationBase { }     public class ConcreteTypeRegistration : ParameterizedRegistrationBase     {         public void SomethingConcrete() { }     }     public class DelegateRegistration : ParameterizedRegistrationBase     {         public void SomethingDelegated() { }     }      public static class Extensions     {         public static ParameterizedRegistrationBase Parameter<T>(             this ParameterizedRegistrationBase p, string name, T value)         {             return p;         }     }      class Program     {         static void Main(string[] args)         {             ConcreteTypeRegistration ct = new ConcreteTypeRegistration();             ct                 .Parameter<int>("age", 20)                 .SomethingConcrete(); // <-- this is not available              DelegateRegistration del = new DelegateRegistration();             del                 .Parameter<int>("age", 20)                 .SomethingDelegated(); // <-- neither is this         }     } } 

If you compile this, you'll get:

'ConsoleApplication16.ParameterizedRegistrationBase' does not contain a definition for 'SomethingConcrete' and no extension method 'SomethingConcrete'... 'ConsoleApplication16.ParameterizedRegistrationBase' does not contain a definition for 'SomethingDelegated' and no extension method 'SomethingDelegated'... 

What I want is for the extension method (Parameter<T>) to be able to be invoked on both ConcreteTypeRegistration and DelegateRegistration, and in both cases the return type should match the type the extension was invoked on.

The problem is as follows:

I would like to write:

ct.Parameter<string>("name", "Lasse")             ^------^             notice only one generic argument 

but also that Parameter<T> returns an object of the same type it was invoked on, which means:

ct.Parameter<string>("name", "Lasse").SomethingConcrete(); ^                                     ^-------+-------^ |                                             | +---------------------------------------------+    .SomethingConcrete comes from the object in "ct"    which in this case is of type ConcreteTypeRegistration 

Is there any way I can trick the compiler into making this leap for me?

If I add two generic type arguments to the Parameter method, type inference forces me to either provide both, or none, which means this:

public static TReg Parameter<TReg, T>(     this TReg p, string name, T value)     where TReg : ParameterizedRegistrationBase 

gives me this:

Using the generic method 'ConsoleApplication16.Extensions.Parameter<TReg,T>(TReg, string, T)' requires 2 type arguments Using the generic method 'ConsoleApplication16.Extensions.Parameter<TReg,T>(TReg, string, T)' requires 2 type arguments 

Which is just as bad.

I can easily restructure the classes, or even make the methods non-extension-methods by introducing them into the hierarchy, but my question is if I can avoid having to duplicate the methods for the two descendants, and in some way declare them only once, for the base class.

Let me rephrase that. Is there a way to change the classes in the first code example above, so that the syntax in the Main-method can be kept, without duplicating the methods in question?

The code will have to be compatible with both C# 3.0 and 4.0.


Edit: The reason I'd rather not leave both generic type arguments to inference is that for some services, I want to specify a parameter value for a constructor parameter that is of one type, but pass in a value that is a descendant. For the moment, matching of specified argument values and the correct constructor to call is done using both the name and the type of the argument.

Let me give an example:

ServiceContainerBuilder.Register<ISomeService>(r => r     .From(f => f.ConcreteType<FileService>(ct => ct         .Parameter<Stream>("source", new FileStream(...)))));                   ^--+---^               ^---+----^                      |                       |                      |                       +- has to be a descendant of Stream                      |                      +- has to match constructor of FileService 

If I leave both to type inference, the parameter type will be FileStream, not Stream.

like image 694
Lasse V. Karlsen Avatar asked May 23 '10 22:05

Lasse V. Karlsen


2 Answers

I wanted to create an extension method that could enumerate over a list of things, and return a list of those things that were of a certain type. It would look like this:

listOfFruits.ThatAre<Banana>().Where(banana => banana.Peel != Color.Black) ... 

Sadly, this is not possible. The proposed signature for this extension method would have looked like:

public static IEnumerable<TResult> ThatAre<TSource, TResult>     (this IEnumerable<TSource> source) where TResult : TSource 

... and the call to ThatAre<> fails because both type arguments need to be specified, even though TSource may be inferred from the usage.

Following the advice in other answers, I created two functions: one which captures the source, and another which allows callers to express the result:

public static ThatAreWrapper<TSource> That<TSource>     (this IEnumerable<TSource> source) {     return new ThatAreWrapper<TSource>(source); }  public class ThatAreWrapper<TSource> {     private readonly IEnumerable<TSource> SourceCollection;     public ThatAreWrapper(IEnumerable<TSource> source)     {         SourceCollection = source;     }     public IEnumerable<TResult> Are<TResult>() where TResult : TSource     {         foreach (var sourceItem in SourceCollection)             if (sourceItem is TResult) yield return (TResult)sourceItem;         }     } } 

This results in the following calling code:

listOfFruits.That().Are<Banana>().Where(banana => banana.Peel != Color.Black) ... 

... which isn't bad.

Notice that because of the generic type constraints, the following code:

listOfFruits.That().Are<Truck>().Where(truck => truck.Horn.IsBroken) ... 

will fail to compile at the Are() step, since Trucks are not Fruits. This beats the provided .OfType<> function:

listOfFruits.OfType<Truck>().Where(truck => truck.Horn.IsBroken) ... 

This compiles, but always yields zero results and indeed doesn't make any sense to try. It's much nicer to let the compiler help you spot these things.

like image 103
CSJ Avatar answered Sep 30 '22 10:09

CSJ


If you have only two specific types of registration (which seems to be the case in your question), you could simply implement two extension methods:

public static DelegateRegistration Parameter<T>(     this DelegateRegistration p, string name, T value);   public static ConcreteTypeRegistration Parameter<T>(     this ConcreteTypeRegistration p, string name, T value);  

Then you wouldn't need to specify the type argument, so the type inference would work in the example you mentioned. Note that you can implement both of the extension methods just by delegation to a single generic extension method with two type parameters (the one in your question).


In general, C# doesn't support anything like o.Foo<int, ?>(..) to infer only the second type parameter (it would be nice feature - F# has it and it's quite useful :-)). You could probably implement a workaround that would allow you to write this (basically, by separating the call into two method calls, to get two places where the type inferrence can be applied):

FooTrick<int>().Apply(); // where Apply is a generic method 

Here is a pseudo-code to demonstrate the structure:

// in the original object FooImmediateWrapper<T> FooTrick<T>() {    return new FooImmediateWrapper<T> { InvokeOn = this; }  } // in the FooImmediateWrapper<T> class (...) Apply<R>(arguments) {    this.InvokeOn.Foo<T, R>(arguments); } 
like image 27
Tomas Petricek Avatar answered Sep 30 '22 09:09

Tomas Petricek