【问题标题】:Delegate "wrong return type"委托“错误的返回类型”
【发布时间】:2016-01-23 23:58:23
【问题描述】:

一方面,我有以下代表:

public delegate IBar FooDelegate(string str);
public delegate IBar FooDelegateWithRef(string str, IBar someBar);

另一方面,我有一个通用类:

public class MyBaseClass
    where T : IBar, new()
{
    public FooDelegate myFunc;
    public FooDelegateWithRef myFuncWithRef;
}

public class MyClass<T> : MyBaseClass
    where T : IBar, new()
{
    public MyClass()
    {
        myFunc = Foo; //"Wrong return type"
        myFuncWithRef = FooWithRef; //"No overload...matches delegate"
    }

    public T Foo(string){ ... }
    public T FooWithRef(string, IBar){ ... }
}

我的问题是当我执行以下操作时:

FooDelegate fooRef = MyClassInstance.Foo;

我收到“错误的返回类型”错误。我知道委托签名必须与方法签名匹配,但是由于泛型中的“where”指令实际上明确指定了 T is IBar,为什么它不起作用?

所以两个问题合二为一: - 为什么编译器拒绝认为方法签名是匹配的? - 更重要的是,我怎样才能使这项工作?出于约定的原因,我更喜欢委托友好的解决方案,而不是使用 Func。

注意:我试图四处寻找答案,但我可能对这个问题的措辞有误,所以如果之前已经回答过,请随意扇我一巴掌。

编辑:正如@Jonathon Chase 指出的那样,我的示例代码并没有完全解决问题。可以在here 找到一个无效的示例。编辑了上面的代码以反映问题。

编辑 2:所有答案对我来说都非常有用,非常感谢您抽出宝贵的时间。如果可以的话,我会检查所有三个!

【问题讨论】:

    标签: c# delegates


    【解决方案1】:

    “错误的返回类型”错误是因为variance 没有support value types。所以实现 IBarclass 可以转换,而实现 struct 则不会:

    class RefTypeBar : IBar {}
    struct ValueTypeBar : IBar {}
    
    FooDelegate f1 = new MyClass<RefTypeBar>().Foo;  // This works
    FooDelegate f2 = new MyClass<ValueTypeBar().Foo; // Fails - wrong return type
    

    错误是在MyClass&lt;T&gt; 内部产生的,因为T 可能是struct,所以编译器不能保证Foo 可以被分配一个FooDelegate。如果将class 约束添加到MyClass&lt;T&gt;,代码将编译。

    public class MyClass<T> : MyBaseClass where T : class, IBar, new()
    

    【讨论】:

    • 我愚蠢地认为使用接口作为通用约束也隐含着“类”的意思——我不知道你可以使用带有值类型的接口。第一个问题解决了,添加类是要走的路。
    【解决方案2】:

    您的示例在这里肯定有其他问题。我目前能够编译并运行以下示例并收到预期的结果:

    public class Program
    {
        public static void Main()
        {
            var x = new MyClass<Bar>();
            FooDelegate test = x.Foo;
            test("Do It");
        }
        public delegate IBar FooDelegate(string str);
        public interface IBar { }
        public class Bar : IBar { }
        public class MyClass<T> where T : IBar, new()
        {
            T item;
            public T Foo(string input) { Console.WriteLine(input); return item; }
        }
    }
    

    DotNetFiddle

    【讨论】:

    • 你说得对,虽然我发布的简短版本会出现问题,但事实并非如此。显然有一些我不知道代表的微妙之处,这是我在 DotNetFiddle 上的一个例子:dotnetfiddle.net/JncI6w
    • 哦,在这种情况下,问题是试图将非泛型委托应用于泛型函数。如果您使用相同的约束使委托泛型应该没问题,或者尝试将其应用于已声明实例上的方法,就像我在上面的代码示例中所做的那样。
    • 但是我不能让它泛型,基类只需要处理接口,并且不知道它的泛型子类:(我要么需要牺牲一部分泛型实现来使用接口而不是 T,或者创建一个与委托签名匹配的虚拟函数,这将破坏其最初的目的。
    【解决方案3】:

    至少在您的 DotNetFiddle 示例中,您可以通过将通用约束更改为 where T: Item, new() 来对 funcA 进行第一次分配。

    在第二个赋值中,委托使用类型 T 作为返回类型和参数类型。我相信这会导致协方差和逆变的有时奇怪的影响 (MSDN about Co/Contravariance): 让我们假设一个泛型实例使用一个类型class SubItem : Item {...} 对于你的类TypedFactory&lt;T&gt;的类型参数T

    可以使用SubItem 作为返回类型,因为返回类型仍然是(子)类型Item,并且委托变量(例如funcA)仍然“满足”由委托类型的声明。

    但是如果我们使用SubItem 作为参数 类型会发生什么?委托变量(例如funcB)不能再在委托类型声明所承诺的每个上下文中调用,例如Item blubb; factory.funcB("I am alive too", blubb) 是不可能的 - 类型不匹配,因为 blubb 不是SubItem 类型的。既然有可能发生这种情况,编译器就不得不在这里抱怨了。

    也许您可以选择将代表设为通用?

    using System;
    
    public interface IItem
    {
        string id {get;set;}
    }
    
    
    public class Item : IItem
    {
        public string id{get;set;}
    }
    
    public class BaseFactory<T>
        where T: IItem, new()
    {
        public DelegateHolder.MakeWithID<T> funcA;
        public DelegateHolder.MakeWithIDAndOther<T> funcB;
    }
    
    public class TypedFactory<T> : BaseFactory<T>
        where T : IItem, new()
    {
    
            public TypedFactory()
            {
                funcA = makeNew;
                funcB = makeNewFromOther;
            }
    
            public T makeNew(string itemId)
            {
                T _item = new T();
                _item.id = itemId;
                return _item;
            }
    
            public T makeNewFromOther(string itemId, T other)
            {
                T _item = new T();
                _item.id = itemId;
                return _item;
            }
    
    }
    
    public class DelegateHolder
    {
        public delegate T MakeWithID<T>(string id) where T: IItem, new();
        public delegate T MakeWithIDAndOther<T>(string id, T other) where T: IItem, new();
    }
    
    public class Program
    {
        public static void Main()
        {
            var x = new TypedFactory<Item>();
            BaseFactory<Item> factory = x;
    
            Item someItem = factory.funcA("I am alive");
    
            Console.WriteLine(someItem.id);
            Console.WriteLine(factory.funcB("I am alive too", someItem).id);
        }
    }
    

    【讨论】:

    • 确实我可以使用泛型委托,但是在基类上我只需要处理接口,所以我不能在这里真正使用这个技巧...... :( 虽然非常有用 -非常感谢!
    猜你喜欢
    • 1970-01-01
    • 2021-02-22
    • 2010-10-29
    • 2013-08-19
    • 2015-05-16
    • 1970-01-01
    • 2015-05-28
    • 1970-01-01
    相关资源
    最近更新 更多