我正在尝试订阅泛型类中的事件。下面是我的扩展List类的代码。 OnEntityDisable
中的操作IEntity
需要IEntity
作为参数。
我收到此错误消息:
预期使用void RemoveItem(IEntity)签名的方法
但是,T
是我班级中的IEntity
,您可以看到:: IList<T> where T : IEntity
。我做错了什么?
public interface IEntity
{
Action<IEntity> OnEntityDisable { get; set; }
}
public class SelectionList<T> : IList<T> where T : IEntity
{
private readonly IList<T> _entities;
public SelectionList()
{
_entities = new List<T>(32);
}
public void Add(T item)
{
if (item == null)
return;
item.OnEntityDisable += RemoveItem; // This line throws an error
_entities.Add(item);
}
public void RemoveItem(T item)
{
if (Count <= 0 || item == null)
return false;
_entities.Remove(item);
}
}
答案 0 :(得分:3)
这是一个逆转问题。每个T
都是IEntity
,但不是每个IEntity
都是T
。您的活动将保证您的论点是IEntity
,但不能保证您是T
。由于RemoveItem
需要T
,因此无效。
使T
逆变不是一个选项,因为SelectionList
是一个类,并且逆变只适用于接口,也因为你不能从List<T>
继承,因为列表的T
1}}不是逆变的。
您有三种方法可以解决它:
更改RemoveItem
您可以更改RemoveItem
接受IEntity
而不是T
:
public void RemoveItem(IEntity item)
{
if (Count <= 0 || item == null)
return;
_entities.Remove((T)item);
}
使用lambda表达式作为事件处理程序
由于更改RemoveItem
会更改类界面,因此可能不是您想要的。您可以通过保持原样来帮助自己,并在lambda表达式中执行必要的类型转换:
item.OnEntityDisable += item => RemoveItem((T)item);
使IEntity
通用
您可以使IEntity
接口通用,并在约束中传递type参数:
public interface IEntity<T>
{
Action<T> OnEntityDisable { get; set; }
}
然后
public class SelectionList<T> : IList<T> where T : IEntity<T>
{
//...
}
答案 1 :(得分:2)
当您订阅接口事件时T
为IEntity
,IEntity
的实施者没有任何限制强制它传递T
作为事件论证。它可以传递任何实现IEntity
的类。 。
有两种解决方案:
IEntity
通用以确保事件参数的类型,并使用限制性更强的类型约束:代码:
public interface IEntity<T>
{
Action<T> OnEntityDisable { get; set; }
}
public class SelectionList<T> : IList<T> where T : IEntity<T>
{ }
代码:
item.OnEntityDisable += e => RemoveItem((T)e);
第一种解决方案更安全,但使用起来可能更麻烦。第二个解决方案依赖于IEntity
的实现,即使没有强制它这样做的类型契约也表现良好。最终取决于您选择哪种解决方案。