在 .NET 4 中使用协方差与接口基本类型?

发布于 2024-08-30 11:15:55 字数 1549 浏览 2 评论 0原文

我有一些使用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

嘿看小鸭子会跑 2024-09-06 11:15:55

协变和逆变只能用于委托和接口声明,因此您的代码将无法工作。但是您可以使用 Cast 扩展方法来转换结果,并使代码正常工作:

List<IValue> ret = LovRepository.ListAllContactTypes(DeletedOptions.NoDeleted)
    .Cast<IValue>().ToList();

这会转换列表中的每个元素并创建一个新的 IValue 元素列表。

您需要这个的原因与类型安全有关。当然,在您的示例代码中,没有问题。但方法 ListAllContactTypes 被声明为返回类型 List。如果您可以将其分配给 List,则可以放入任何 IValue - 因此,如果其他代码期望列表显式包含 ContactType,则该代码将中断。考虑这个例子:

List<ContactType> listOfContactType =  // Some method ....
List<IValue> list = listOfContactType; // This line not allowed.

如果编译了,您将能够执行以下操作:

list.Insert(0,new DummyType());

但是您仍然必须考虑原始引用,应该允许它执行以下操作:

ContactType contact = listOfContactType[0];  // Woops, element is not 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:

List<IValue> ret = LovRepository.ListAllContactTypes(DeletedOptions.NoDeleted)
    .Cast<IValue>().ToList();

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:

List<ContactType> listOfContactType =  // Some method ....
List<IValue> list = listOfContactType; // This line not allowed.

If that compiled, you would be able to do:

list.Insert(0,new DummyType());

But you still have to consider the original reference, on which it should be allowed to do:

ContactType contact = listOfContactType[0];  // Woops, element is not ContactType.

You can't do this, because the element in the list is DummyType. Thankfully the compiler saved us early.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文