Home > Software design >  Generic C# method with type constraint does not compile
Generic C# method with type constraint does not compile

Time:02-12

I have an extension method with a generic type constraint. To my surprise, this code does not compile.

namespace N
{
    public interface M
    {
    }

    public class X : M
    {
        public static void DoSomething()
        {
            var x = new X();
            // ERROR: CS0407 'M X.DoSomethingWith(M)' has the wrong return type
            x.Configure(DoSomethingWith);
        }

        private static M DoSomethingWith(M obj)
        {
            return obj;
        }
    }

    public static class Extensions
    {
        public static T Configure<T>(this T obj, Func<T, T> action) where T : M
        {
            return action(obj);
        }
    }
}

A similar construct, with a void-typed extension method, does compile:

    public interface M
    {
    }

    public class X : M
    {
        public static void DoSomething()
        {
            var x = new X();

            // compiles fine
            x.Configure(DoSomethingWith);
        }

        private static void DoSomethingWith(M obj)
        {
        }
    }

    public static class Extensions
    {
        public static void Configure<T>(this T obj, Action<T> action) where T : M
        {
        }
    }

What is happening here and why is the first version not valid?

CodePudding user response:

This is due to the variance support in delegates for matching method signatures:

This means that you can assign to delegates not only methods that have matching signatures, but also methods that return more derived types (covariance) or that accept parameters that have less derived types (contravariance) than that specified by the delegate type. This includes both generic and non-generic delegates.

x.Configure(DoSomethingWith) will be resolved at compile time as Configure<X> and the input parameter of Action<T> (and Func<T,TResult> also) is contravariant (i.e. since X is M it is fine to up-cast it for DoSomethingWith) while the return type of DoSomethingWith is not guaranteed to be X (for Func<T,TResult> case).

There are multiple solutions, for example you can explicitly specify generic type parameter for Configure as M:

x.Configure<M>(DoSomethingWith);

Or change signature of Configure:

private static X DoSomethingWith(X obj) => return obj;

Note that changing only return type of DoSomethingWith will also work due to the variance in the delegates (though may not fit your desired logic):

private static X DoSomethingWith(M obj) => obj as X ?? new X();

CodePudding user response:

Because you "action" in Configure is of type Func<T, T>, which means that we receive T and return T. The factual type of T is X, not M. Method "DoSomething" returns in fact M, which cannot be casted implicitly to X, because X is more specific type. So what C# tries to do with your expression is to find a method "DoSomething", which accepts X and returns X, and there is no such method.

  • Related