Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a workaround to C# not being able to infer generic type arguments using type constraints?

Eric Lippert has explained in his blog post at http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx why constraints are not considered for type inference, which makes sense given that methods cannot be overloaded by simply changing type constraints. However, I would like to find a way to instantiate an object using two generic types, one which can be inferred and another which could be inferred if constraints were considered, without having to specify any of the types.

Given the types:

public interface I<T>
{
    Other<T> CreateOther();
}

public class C : I<string>
{
    public Other<string> CreateOther()
    {
        return new Other<string>();
    }
}

public class Other<T>
{
}

and the factory:

public static class Factory1
{
    public static Tuple<T, Other<T1>> Create<T, T1>(T o) where T : I<T1>
    {
        return new Tuple<T, Other<T1>>(o, o.CreateOther());
    }
}

the following desired code will not compile:

    public void WontCompile()
    {
        C c = new C();
        var v = Factory1.Create(c); // won't compile
    }

The error message is "error CS0411: The type arguments for method 'yo.Factory1.Create(T)' cannot be inferred from the usage. Try specifying the type arguments explicitly.", which is in line with what Eric said in his blog post.

Thus, we can simply specify the generic type arguments explicitly, as the error message suggests:

    public void SpecifyAllTypes()
    {
        C c = new C();
        var v = Factory1.Create<C, string>(c); // type is Tuple<C, Other<string>>
    }

If we don't wish to specify type arguments and we don't need to retain type C, we can use the following factory:

public static class Factory2
{
    public static Tuple<I<T1>, Other<T1>> CreateUntyped<T1>(I<T1> o)
    {
        return new Tuple<I<T1>, Other<T1>>(o, o.CreateOther());
    }
}

and now specify:

    public void Untyped()
    {
        C c = new C();
        var v = Factory2.CreateUntyped(c); // type is Tuple<I<string>, Other<string>>
    }

However, I wish to retain type C in the returned object and not specify the types.

like image 817
jam40jeff Avatar asked Dec 22 '12 15:12

jam40jeff


1 Answers

I came up with a solution to this problem, but it seems to be a kludge of a workaround, where the object of type C is used twice in a two-step factory call.

To do this, the following factories are used:

public static class Factory3
{
    public static Factory<T1> CreateFactory<T1>(I<T1> o)
    {
        return new Factory<T1>();
    }
}

public class Factory<T1>
{
    public Tuple<T, Other<T1>> Create<T>(T o) where T : I<T1>
    {
        return new Tuple<T, Other<T1>>(o, o.CreateOther());
    }
}

which can then be used as follows:

    public void Inferred()
    {
        C c = new C();
        var v = Factory3.CreateFactory(c).Create(c); // type is Tuple<C, Other<string>>
    }

This just feels odd since c is used twice. The first time it is used it is actually discarded as it is just being used to infer the base type argument.

Is there a better solution to this problem where the object does not need to be used twice and the types do not need to be specified?

edit: I just realized that, although the object must be used twice, the second factory class is not needed. Rather, both parameters could just be used in the same factory method as follows:

public class Factory
{
    public Tuple<T, Other<T1>> Create<T, T1>(T o, I<T1> o2) where T : I<T1>
    {
        return new Tuple<T, Other<T1>>(o, o.CreateOther());
    }
}

This would be used as follows:

public void Inferred()
{
    C c = new C();
    var v = Factory.Create(c, c); // type is Tuple<C, Other<string>>
}

It's still not ideal, but better than having to create a second factory class, and at least XMLDoc comments could be used to indicate that both parameters should be the same object. Once again, the one parameter (o2 in this case) is only used to infer the constrained types for T.

like image 190
jam40jeff Avatar answered Nov 14 '22 03:11

jam40jeff