在 .NET 4 中使用协方差与接口基本类型?
我有一些使用 LINQ-to-SQL 创建的实体。其中六个实体(主要代表下拉列表中的值)实现了一个我称为 IValue
的接口。我这样做是因为 UI 层必须考虑一些特殊情况 - 特别是,如果记录上的原始值已被标记为已删除,则显示什么。
存储库为这些人提供了各种 ListAllXXX
方法。所有这些都返回输入到适当实体类型的通用列表。一个例子:
public static List<ContactType> ListAllContactTypes(DeletedOptions getDeleted)
{ /* requisite code */ }
当然,ContactType
确实实现了IValue
。
还有另一组服务旨在实际检索特定于 UI 的列表。基本模式是这样的:(
// One of these for each entity type
public static List<IValue> GetContactTypeList(ContactType target)
{
List<IValue> ret = LovRepository.ListAllContactTypes(DeletedOptions.NoDeleted);
PrepList(ret, target);
return ret;
}
// All of the above methods use this guy
private static void PrepList(List<IValue> list, IValue targetEntity)
{
list.Insert(0, new DummyValue() { Description = "Add New ... ", ID = 0 });
if (targetEntity != null && !(list.Contains(targetEntity))
list.Add(new DummyValue() { Description = "[deleted]", ID = -1 });
}
我可能应该注意到 DummyValue
是我创建的一个简单的类,它也实现了 IValue
,其生命的全部目的是充当“添加新的”和“删除的”菜单选项。)
所有这一切的发生是因为我不想编写几十行几乎相同的代码——这就是我认为我们存在协方差的全部原因。
此处编写的代码无法编译。我尝试在 ListAllContactTypes
行上手动转换为 List
;可以编译,但在运行时失败并出现无效的强制转换异常。
我怎样才能到达我想去的地方?使用接口的通用方差是否有限制?如果是这样,有没有简单的方法可以解决它?如果不是,我是否只能编写一堆高度重复但略有不同的代码? (我真的试图避免这种情况。)
这很可能是重复的,但我的 Google-fu 现在让我失望了。如果是,请投票以相应关闭。 (如果是这样的话,我会投出接近的一票!)
I have some entities created with LINQ-to-SQL. Six of these entities (representing values primarily in drop-down lists) implement an interface I've called IValue
. I did this because the UI layer is going to have to account for a couple special cases -- notably, what to display if the original value on a record has been flagged as deleted.
The repository has a variety of ListAllXXX
methods for these guys. All of these return generic lists typed to the appropriate entity type. An example:
public static List<ContactType> ListAllContactTypes(DeletedOptions getDeleted)
{ /* requisite code */ }
ContactType
does implement IValue
, of course.
There's another set of services designed to actually retrieve the UI-specific list. The basic pattern is thus:
// One of these for each entity type
public static List<IValue> GetContactTypeList(ContactType target)
{
List<IValue> ret = LovRepository.ListAllContactTypes(DeletedOptions.NoDeleted);
PrepList(ret, target);
return ret;
}
// All of the above methods use this guy
private static void PrepList(List<IValue> list, IValue targetEntity)
{
list.Insert(0, new DummyValue() { Description = "Add New ... ", ID = 0 });
if (targetEntity != null && !(list.Contains(targetEntity))
list.Add(new DummyValue() { Description = "[deleted]", ID = -1 });
}
(I should probably note that DummyValue
is a simple class I created that also implements IValue
, and whose whole purpose in life is to serve as the "add new" and "deleted" menu options.)
All of this comes about because I didn't want to write a few dozen lines of almost identical code -- the whole reason I thought we had covariance.
The code as written here does not compile. I've tried a manual cast to List<IValue>
on the ListAllContactTypes
line; that compiles, but fails at run-time with an invalid cast exception.
How can I get where I want to go here? Is there a limitation on using generic variance with interfaces? If so, is there an easy way around it? If not, am I relegated to writing a bunch of highly-repetitive-but-just-slightly-different code? (Which I'm really trying to avoid.)
This may well be a duplicate, but my Google-fu is failing me right now. If it is, please vote to close accordingly. (I'll pile on a close vote if that's the case!)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
协变和逆变只能用于委托和接口声明,因此您的代码将无法工作。但是您可以使用 Cast 扩展方法来转换结果,并使代码正常工作:
这会转换列表中的每个元素并创建一个新的 IValue 元素列表。
您需要这个的原因与类型安全有关。当然,在您的示例代码中,没有问题。但方法 ListAllContactTypes 被声明为返回类型 List。如果您可以将其分配给 List,则可以放入任何 IValue - 因此,如果其他代码期望列表显式包含 ContactType,则该代码将中断。考虑这个例子:
如果编译了,您将能够执行以下操作:
但是您仍然必须考虑原始引用,应该允许它执行以下操作:
您不能这样做,因为列表中的元素是 DummyType。幸亏编译器早点救了我们。
Co- and contravariance can only be used on delegate and interface declarations, so your code won't work. But you can cast your result using the Cast extension method, and make your code work:
This casts each element in the list and creates a new list of IValue elements.
The reason why you need this, has to do with type safety. In your example code of course, there is no problem. But the method ListAllContactTypes are declared to return type List<ContactType>. If you could just assign it to List<IValue>, you can put any IValue in - so if some other code expects the list to contain ContactType explicitly, that code will break. Consider this example:
If that compiled, you would be able to do:
But you still have to consider the original reference, on which it should be allowed to do:
You can't do this, because the element in the list is DummyType. Thankfully the compiler saved us early.