9

我无法理解为什么以下代码段没有给我错误

public void SomeMethod<T>(T arg) where T : MyInterface
{
  MyInterface e = arg;
}

但是这个,由于泛型类型约束,我希望它可以工作

private readonly IList<Action<MyInterface>> myActionList = new List<Action<MyInterface>>();

public IDisposable Subscribe<T>(Action<T> callback) where T: MyInterface
{
  myActionList.Add(callback); // doesn't compile
  return null
}

给出这个错误

cannot convert from 'System.Action<T>' to 'System.Action<MyInterface>'

我正在使用 VS2012 sp1 和 .NET 4.5。

谁能解释为什么约束不允许编译?

4

6 回答 6

5

这是一个逆变问题 - anAction<MyInterface>应该能够将任何MyInterface实例作为参数,但是您正在尝试存储Action<T>whereT是 的某个子类型MyInterface,这是不安全的。

例如,如果您有:

public class SomeImpl : MyInterface { }
public class SomeOtherImpl : MyInterface { }
List<Action<MyInterface>> list;

list.Add(new Action<SomeImpl>(i => { }));
ActionMyInterface act = list[0];
act(new SomeOtherImpl());

如果 type比 type '小',你只能分配Action<T>给 some 。例如Action<U>TU

Action<string> act = new Action<object>(o => { });

是安全的,因为字符串参数在对象参数所在的位置始终有效。

于 2013-03-25T09:12:06.913 回答
3

Classes and delegates are not the same thing. System.Action<MyInterface> represents a function with a single parameter of type MyInterface whilst System.Action<T> represents a method with a parameter of type T : MyInterface. The function signatures are not compatible, it is not relevent that T is a derivative of MyInterface, the signature would only be compatible if T was exactly MyInterface.

于 2013-03-25T09:12:55.080 回答
2

where T: MyInterface约束意味着“实现 MyInterface的任何类或结构的任何实例”。

因此,您尝试做的事情可以简化为:

Action<IList> listAction = null;
Action<IEnumerable> enumAction = listAction;

哪个不应该工作,而仍然IList : IEnumerable. 更多详情可在这找到:

http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx http://msdn.microsoft.com/en-us/library/dd799517.aspx

因此,如果您真的需要使用泛型而不仅仅是接口 - 您可以这样做,尽管它会增加复杂性和较小的性能问题:

public static IDisposable Subscribe<T>(Action<T> callback) where T : MyInterface
{
    myActionList.Add(t => callback((T)t)); // this compiles and work
    return null;
}
于 2013-03-25T09:19:29.887 回答
2

I find it helpful in these situations to consider what goes wrong if you allow the behaviour. So let's consider that.

interface IAnimal { void Eat(); }
class Tiger : IAnimal 
{ 
  public void Eat() { ... }
  public void Pounce() { ... } 
}
class Giraffe : IAnimal 
...
public void Subscribe<T>(Action<T> callback) where T: IAnimal
{
   Action<IAnimal> myAction = callback; // doesn't compile but pretend it does.
   myAction(new Giraffe()); // Obviously legal; Giraffe implements IAnimal
}
...
Subscribe<Tiger>((Tiger t)=>{ t.Pounce(); });

So what happens? We create a delegate that takes a tiger and pounces, pass that to Subscribe<Tiger>, convert that to Action<IAnimal>, and pass a giraffe, which then pounces.

Obviously that has to be illegal. The only place where it is sensible to make it illegal is the conversion from Action<Tiger> to Action<IAnimal>. So that's where it is illegal.

于 2013-03-25T14:52:22.453 回答
1

类和委托的行为略有不同。让我们看一个简单的例子:

public void SomeMethod<T>(T arg) where T : MyInterface
{
  MyInterface e = arg;
}

在这种方法中,您可以假设 T 至少为MyInterface,因此您可以执行类似的操作,MyInterface e = arg;因为 args 始终可以转换为MyInterface

现在让我们看看代表的行为:

public class BaseClass { };
public class DerivedClass : BaseClass { };
private readonly IList<Action<BaseClass >> myActionList = new List<Action<BaseClass>>();

public void Subscribe<T>(Action<T> callback) where T: BaseClass
{
  myActionList.Add(callback); // so you could add more 'derived' callback here Action<DerivedClass>
  return null;
}

现在我们向 myActionList 添加 DerivedClass 回调,然后在某个地方调用委托:

foreach( var action in myActionList ) {
   action(new BaseClass);
}

但是你不能这样做,因为如果你有 DerivedClass 回调,你必须将它作为参数传递 DerivedClass。

这个问题是指Covariance and contravariance。你可以从这篇文章中了解方差,Eric Lippert 也有关于方差的非常有趣的文章,是第一篇文章,你可以在他的博客中找到其余的文章。

PS 根据李的评论编辑。

于 2013-03-25T09:39:32.973 回答
0

如果T无论如何仅限于某个接口,您可以只使用该接口来代替:

public void SomeMethod(MyInterface arg)
{
  MyInterface e = arg;
}

private readonly IList<Action<MyInterface>> myActionList = new IList<Action<MyInterface>>();

public IDisposable Subscribe(Action<MyInterface> callback)
{
  myActionList.Add(callback); // does compile
  return null
}

将工作和编译,实际上与您现在拥有的相同。

如果您想对类型进行相同的操作,则泛型很有用,如果您将类型限制为某个接口,则您已经破坏了泛型的目的,并且可能应该只使用该接口。

于 2013-03-25T09:10:21.987 回答