【问题标题】:Cast concrete class to generic base interface将具体类转换为通用基接口
【发布时间】:2009-11-22 06:46:56
【问题描述】:

这是我面临的情况:

public abstract class Record { }

public abstract class TableRecord : Record { }

public abstract class LookupTableRecord : TableRecord { }

public sealed class UserRecord : LookupTableRecord { }

public abstract class DataAccessLayer<TRecord> : IDataAccessLayer<TRecord>
    where TRecord : Record, new() { }

public abstract class TableDataAccessLayer<TTableRecord> : DataAccessLayer<TTableRecord>, ITableDataAccessLayer<TTableRecord>
    where TTableRecord : TableRecord, new() { }

public abstract class LookupTableDataAccessLayer<TLookupTableRecord> : TableDataAccessLayer<TLookupTableRecord>, ILookupTableDataAccessLayer<TLookupTableRecord>
    where TLookupTableRecord : LookupTableRecord, new() { }

public sealed class UserDataAccessLayer : LookupTableDataAccessLayer<UserRecord> { }

public interface IDataAccessLayer<TRecord>
    where TRecord : Record { }

public interface ITableDataAccessLayer<TTableRecord> : IDataAccessLayer<TTableRecord>
    where TTableRecord : TableRecord { }

public interface ILookupTableDataAccessLayer<TLookupTableRecord> : ITableDataAccessLayer<TLookupTableRecord>
    where TLookupTableRecord : LookupTableRecord { }

现在,当我尝试执行以下转换时,它不会编译:

UserDataAccessLayer udal = new UserDataAccessLayer();
            ITableDataAccessLayer<TableRecord> itdal = (ITableDataAccessLayer<TableRecord>)udal;

但是,当我执行以下强制转换时,它编译时不会出现运行时错误:

UserDataAccessLayer udal = new UserDataAccessLayer();
            ITableDataAccessLayer<UserRecord> itdal = (ITableDataAccessLayer<UserRecord>)udal;

我真的需要使用基本的ITableDataAccessLayer&lt;TableRecord&gt; 接口,因为我不知道具体的类型。

希望这是描述性的,足以回答我的问题。

【问题讨论】:

  • 我认为你有一个错字 - 编译/不编译的两个代码部分是相等的。
  • 您好。抱歉没有正确格式化html。感谢您指出了这一点。请查看帖子增益。谢谢!
  • 它们还是一样的。

标签: c# interface generics casting base


【解决方案1】:

.NET 4.0 支持您尝试执行的操作,但 3.5 不支持。它被称为generic covariance。与此同时,您可以做的是创建一个名为 ITableDataAccessLayer 的非通用接口(在您使用 T 的任何地方使用 Object 类型)并提供显式接口实现。这是 .NET 中处理它的泛型类型的数量。

【讨论】:

  • 嗨乔希,感谢您的回复。但是如果创建一个非泛型接口,那将允许使用任何对象代替 T。我只想允许从 TableRecord 继承的对象。
  • 嗯,不,不是真的。这将导致运行时错误,因为您的非泛型实现通常会简单地使用强制转换调用泛型实现。或者,您可以在 UserDataAccessLayer 中显式实现 ITableDataAccessLayer。这是相同的概念,只是您只是缩小了编译器将接受的类型。
【解决方案2】:

确实,您需要协方差。几个点。

首先,了解为什么有时这是非法的。以 IList 为例。假设你有一个IList&lt;Giraffe&gt;,一个长颈鹿列表。你能把它转换成动物列表吗?不,不安全。是的,长颈鹿列表是动物列表,因为列表中的所有内容都是动物。但是列表是可变的;您可以将老虎放入动物列表中,但如果它确实是长颈鹿列表,那么这必须失败。由于这不安全,我们不会在 C# 4 中使 IList 协变。

其次,如果您对这个主题感兴趣,您可能想阅读我关于如何设计该功能以保持类型安全的长系列博客文章。

http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

第三,仅供参考,我将在接下来的几周内在我的博客上发布我们用来计算接口何时可以安全地协变或逆变的确切规则。

【讨论】:

  • 我知道此时这并不重要,但我认为它应该被允许并且只是导致运行时异常。如果我的一个也是唯一一个符合 .NET 的存在,那么它会试图保护开发人员免受过多的伤害。另一个重要的例子是在 switch 语句中强制使用 break 语句,如果这就是我想要的,为什么我不能让 case 失败?或者有 switch(variable, true) 以允许大小写通过等。
  • 好的。描述您希望运行时如何在运行时确定何时抛出异常。现在描述您提议的更改的总性能成本:对每次写入、每次方法调用等进行类型检查。请注意,100% 的性能成本由正确的代码承担。现在尝试将其出售给已经认为“托管代码等于慢代码”的持怀疑态度的开发人员群体。我们不会在编译时抓住这些东西来对你做家长式对待,我们这样做是因为否则我们必须让你的代码非常慢。
  • 至于你关于失败的问题:失败是一个坏主意,因为它几乎从来都不是用户真正想要的,而且这是一个无形的错误。如果你想失败,你可以很容易地明确表示:只需说“goto case default;”如果您想使用默认情况。
【解决方案3】:

这会编译吗?

UserDataAccessLayer udal = new UserDataAccessLayer(); 
ITableDataAccessLayer<TTableRecord> itdal = (ITableDataAccessLayer<TTableRecord>)udal;

甚至只是

ITableDataAccessLayer<TTableRecord> itdal = new UserDataAccessLayer(); 

因为它是一个泛型接口,它可能需要知道它是什么类型?

了解错误消息也会很有帮助。这通常会阐明主题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-14
    • 1970-01-01
    • 2021-12-25
    相关资源
    最近更新 更多