2

我有一些使用 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,它的全部目的是充当“添加新”和“删除”菜单选项。)

所有这一切都是因为我不想编写几十行几乎相同的代码——这就是我认为我们有协方差的全部原因。

此处编写的代码无法编译。我试过手动投到List<IValue>ListAllContactTypes行了;编译,但在运行时失败并出现无效的强制转换异常。

我怎样才能到达我想去的地方?对接口使用泛型变量是否有限制?如果是这样,有没有简单的方法解决它?如果不是,我是否会沦为编写一堆高度重复但略有不同的代码?(我真的很想避免。)

这很可能是重复的,但我的 Google-fu 现在让我失望了。如果是,请相应地投票关闭。(如果是这样的话,我会投票赞成!)

4

1 回答 1

1

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

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

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

您需要这个的原因与类型安全有关。当然,在您的示例代码中,没有问题。但是方法 ListAllContactTypes 被声明为返回类型 List<ContactType>。如果您可以将其分配给 List<IValue>,则可以将任何 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。谢天谢地,编译器早早地救了我们。

于 2010-04-27T20:15:54.143 回答