代码之家  ›  专栏  ›  技术社区  ›  Jim Jeffries

Action的通用约束未按预期工作

  •  9
  • Jim Jeffries  · 技术社区  · 13 年前

    我很难理解为什么下面的代码段没有给我一个错误

    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。

    有人能解释一下为什么约束不允许编译这个吗?

    6 回复  |  直到 13 年前
        1
  •  5
  •   Lee    13 年前

    这是一个相反的问题 Action<MyInterface> 应该可以接受任何 MyInterface 实例作为参数,但是您试图存储 Action<T> 哪里 T 是的某个子类型 我的界面 ,这是不安全的。

    例如,如果您有:

    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());
    

    您只能分配 动作<T> 对一些人来说 Action<U> 如果类型 T 比类型“小” U 例如

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

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

        2
  •  3
  •   odyss-jii    13 年前

    类和代理不是一回事。 System.Action<MyInterface> 表示具有类型为的单个参数的函数 MyInterface 同时 System.Action<T> 表示具有类型参数的方法 T : MyInterface 。函数签名不兼容,这与 T 是的衍生物 我的界面 ,签名只有在以下情况下才兼容 T 正是 我的界面 .

        3
  •  2
  •   Lanorkin    13 年前

    这个 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;
    }
    
        4
  •  2
  •   Eric Lippert    13 年前

    我发现在这种情况下,如果你允许这种行为,考虑一下会出什么问题是有帮助的。所以让我们考虑一下。

    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(); });
    

    那么会发生什么呢?我们创建一个代表,它抓住老虎并猛扑,然后将其传递给 Subscribe<Tiger> ,将其转换为 Action<IAnimal> ,然后经过一只长颈鹿,然后长颈鹿猛扑过去。

    显然这是违法的。唯一明智的做法是将其定为非法 Action<Tiger> 动作<I尼马尔> 。所以这就是违法的地方。

        5
  •  1
  •   Andrew    13 年前

    类和代理的行为有点不同。让我们看一个简单的例子:

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

    在这个方法中,你可以假设T至少是 MyInterface ,所以你可以这样做 MyInterface e = arg; 因为args总是可以强制转换为 我的界面 .

    现在,让我们看看代理的行为:

    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;
    }
    

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

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

    但您不能这样做,因为如果您有DerivedClass回调,您必须将其作为参数传递给DerivedCClass。

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

    附言根据Lee的评论进行编辑。

        6
  •  0
  •   Bazzz    13 年前

    如果 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
    }
    

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

    如果您想对类型进行相同的REGARDLESS操作,如果您将类型限制在某个接口上,那么泛型是有用的——您已经破坏了泛型的目的,可能只应该使用该接口。

    推荐文章