'DoSomethingInt' 没有重载匹配委托 'DoSomethingDelegate<T>'

No overload for 'DoSomethingInt' matches delegate 'DoSomethingDelegate<T>'

class DelegateTest<T> where T: // ....
{
    public DelegateTest(DoSomethingDelegate<T> action = null)
    {
        if (action != null)
            _doSomething = action;
        else if (typeof(T) == typeof(int))
            //_doSomething = DoSomethingInt; // fails
            //_doSomething = (DoSomethingDelegate<T>)DoSomethingInt; // fails
            //_doSomething = (DoSomethingDelegate<T>)((Delegate)DoSomethingInt); // fails
            _doSomething = (val) => DoSomethingInt((int)((object)val)); // too ugly
        else //  ...
    }

    public void DoSomethingVeryComplex(T val)
    {
        // ....
        _doSomething(val);
        // ....
    }

    DoSomethingDelegate<T> _doSomething;

    static void DoSomethingInt(int val)
    {
    }
}

delegate void DoSomethingDelegate<T>(T val);

因此,委托和方法在签名上是兼容的,但是由于某些不明原因,此分配不起作用,而且我完全不知道如何让它起作用。除了创建 shim 函数之外还有更好的想法吗?
这个问题不是关于如何用其他方式重写这段代码的问题。这只是关于如何完成这项任务,如果那是不可能的 - 为什么。

如果您使用 Action<T> 而不是自定义委托,则可以直接转换和分配委托。

class DelegateTest<T>
{
    public DelegateTest()
    {
        if (typeof(T) == typeof(int))
        {
            _doSomething = (Action<T>)(object)DoSomethingInt;
        }
    }

    Action<T> _doSomething;

    public void InvokeDoSomething(T x) => _doSomething(x);

    static void DoSomethingInt(int val)
    {
        Console.WriteLine("Hello world! The argument was {0}", val);
    }
}

快速测试:

public class Program
{
    public static async Task Main()
    {
        var o = new DelegateTest<int>();
        o.InvokeDoSomething(1);
    }
}

输出:

Hello world! The argument was 1

Fiddle

类似于 John Wu 的 ,但也适用于 DoSomethingDelegate<T>

static void Main()
{
    new DelegateTest<int>().InvokeDoSomething(1);
    new DelegateTest<string>().InvokeDoSomething("Hello");
}

delegate void DoSomethingDelegate<T>(T val);

class DelegateTest<T>
{
    public DelegateTest()
    {
        if (typeof(T) == typeof(int))
            _doSomething = new DoSomethingDelegate<T>((Action<T>)(object)DoSomethingInt);
        else if (typeof(T) == typeof(string))
            _doSomething = new DoSomethingDelegate<T>((Action<T>)(object)DoSomethingString);
        else throw new NotSupportedException();
    }

    DoSomethingDelegate<T> _doSomething;

    public void InvokeDoSomething(T x) => _doSomething(x);

    void DoSomethingInt(int val) => Console.WriteLine($"DoSomethingInt({val})");
    void DoSomethingString(string val) => Console.WriteLine($"DoSomethingString({val})");
}

输出:

DoSomethingInt(1)
DoSomethingString(Hello)

Try it on Fiddle.


备选方案 1: 正如 Enigmativity 在 中提到的,将 DoSomethingInt 转换为 DoSomethingDelegate<T> 也可以这样实现:

public DelegateTest()
{
    if (typeof(T) == typeof(int))
        _doSomething = (DoSomethingDelegate<T>)(Delegate)(DoSomethingDelegate<int>)DoSomethingInt;
    else if (typeof(T) == typeof(string))
        _doSomething = (DoSomethingDelegate<T>)(Delegate)(DoSomethingDelegate<string>)DoSomethingString;
    else throw new NotSupportedException();
}

Try it on Fiddle.

与我最初的建议相比,它还使委托的调用速度提高了大约 20%,原因由 Matthew Watson 解释。我最初的建议涉及两次方法调用,而不是一次。


备选方案 2: John Wu 的变体,使用 switch 语句而不是 if+as :

public DelegateTest()
{
    switch (this)
    {
        case DelegateTest<int> self: self._doSomething = DoSomethingInt; break;
        case DelegateTest<string> self: self._doSomething = DoSomethingString; break;
        default: throw new NotSupportedException();
    }
}

Try it on Fiddle.

这个是类型安全的。您不能意外地将错误的方法分配给错误的类型。调用委托与之前的替代方案 1 一样快。

首先赋值的问题是,您在 DelegateTest<T> 的范围内,其中 T 可以是任何类型,并且您要求编译器允许某些类型DoSomethingDelegate<int> 分配给 DoSomethingDelegate<T>。如果 T 在 run-time 处是 int,那很好,但编译器不知道这一点。尽管 run-time 检查了类型,但这是非法转换。

您能做的最好的事情就是让您的代码对编码错误更有弹性。这是 Theo 答案的变体,它使代码更多 strongly-typed。这可能是您无需反思就能得到的最好结果。

static void Main()
{
    new DelegateTest<int>().InvokeDoSomething(1);
    new DelegateTest<string>().InvokeDoSomething("Hello");
}

delegate void DoSomethingDelegate<T>(T val);

class DelegateTest<T>
{
    private Dictionary<Type, Delegate> _delegates = new Dictionary<Type, Delegate>();

    private void Register<K>(DoSomethingDelegate<K> @delegate)
    {
        _delegates[typeof(K)] = @delegate;
    }
    
    public DelegateTest()
    {
        this.Register<int>(DoSomethingInt);
        this.Register<string>(DoSomethingString);
        if (_delegates.ContainsKey(typeof(T)))
        {
            _doSomething = (DoSomethingDelegate<T>)_delegates[typeof(T)];
        }
        else throw new NotSupportedException();
    }

    DoSomethingDelegate<T> _doSomething;

    public void InvokeDoSomething(T x) => _doSomething(x);

    void DoSomethingInt(int val) => Console.WriteLine($"DoSomethingInt({val})");
    void DoSomethingString(string val) => Console.WriteLine($"DoSomethingString({val})");
}

输出:

DoSomethingInt(1)
DoSomethingString(Hello)

因为你知道 T,你可以将 this 转换为特定的泛型类型 (DelegateTest<int>)。获得该引用后,您可以使用它 type-safe 和 type-specific 访问其方法、属性和字段,甚至是私有的。

class DelegateTest<T>
{
    public DelegateTest()
    {
        if (typeof(T) == typeof(int))
        {
            var self = this as DelegateTest<int>; //Magic!!
            self._doSomething = DoSomethingInt;
        }
    }

    DoSomethingDelegate<T> _doSomething;

    public void InvokeDoSomething(T x) => _doSomething(x);

    static void DoSomethingInt(int val)
    {
        Console.WriteLine("Hello. The argument was {0}", val);
    }
}

delegate void DoSomethingDelegate<T>(T val);

快速测试:

public class Program
{
    public static async Task Main()
    {
        var o = new DelegateTest<int>();
        o.InvokeDoSomething(1);
    }
}

输出:

Hello. The argument was 1

Link to DotNetFiddle