2016-01-23 154 views
1

一方面我有以下的委托:代表“错误的返回类型为”

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 IBar,为什么它不起作用?

因此,两个问题之一: - 为什么编译器拒绝考虑方法签名匹配? - 更重要的是,我该如何做这项工作?我宁愿使用代理友好的解决方案,而不是出于约定的原因使用Func。

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

编辑:正如@Jonathon Chase指出的,我的示例代码并没有完全包装这个问题。一个不起作用的例子可以发现here。编辑上面的代码以反映问题。

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

回答

3

“错误的返回类型” 的错误是因为variancesupport value types。因此,一个class实施IBar可以转换,而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<T>因为T可能是一个struct内产生的错误,使编译器不能保证该Foo能被分配一个FooDelegate。如果您将class约束添加到MyClass<T>,则代码将被编译。

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

愚蠢的我认为使用接口作为通用约束也隐含地意味着'类' - 我不知道你可以使用接口价值类型首先解决的问题是增加类是要走的路。 – Nebu

3

在这里你的例子必须有别的东西在进行。我目前能够编译和运行下面的示例,并收到预期的结果:通过改变通用

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

+0

你说得对,我虽然认为这个问题会出现在我发布的短版本中,但它没有。 实际上我有一些微妙的东西没有与委托意识到,这里是我的DotNetFiddle的问题的例子:https://dotnetfiddle.net/JncI6w – Nebu

+0

哦,在这种情况下,问题是试图申请一个非泛型委托给一个通用函数。如果您使用相同的约束使委托通用,它应该没问题,或者试图应用于已经声明的实例上的方法,就像我在上面的代码示例中所做的一样。 –

+0

但我不能使它通用,基类只能处理接口,并且不知道它的通用子类:( 我需要牺牲通用实现的某些部分来使用接口的T,或者创建一个与委托签名匹配的虚拟函数,并且这将破坏它的初始目的。 – Nebu

2

至少在你的DotNetFiddle例如,你可以做第一个分配给funcA可能约束到where T: Item, new()

在第二个赋值中,委托使用类型T既作为返回类型又作为参数类型。我相信这会导致协变和反变换的有时奇怪的影响(MSDN about Co/Contravariance): 让我们假设一个类属实例使用类型class SubItem : Item {...} 作为您的类TypedFactory<T>的类型参数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); 
    } 
} 
+0

确实,我可以使用泛型委托,但是在基类中我只需要处理接口,所以我可以'在这里真的使用这个技巧...... :( 虽然非常翔实 - 非常感谢! – Nebu