此帖子继续this one。
我试图了解我是否是唯一一个错过并需要.NET泛型类型才能继承其通用参数类型的人。
挑战是收集有利于此功能的令人信服的理由,或者,要知道没有。
我有理由将其作为这个问题的答案 - 见下文。
我要求那里的人添加他们作为这篇文章的答案。
如果您不同意该功能有用或没有充分理由支持 - 请不要在此处发布任何内容,但您可以在已启动该功能的原始帖子中进行此操作 - here。
P.S。
某些C ++模式在.NET中无关紧要。例如,在他出色的着作Modern C++ Design中,Andrei Alexandrescu描述了如何创建在编译时评估的类型列表。当然,这种模式与.NET无关,如果我需要一个类型列表,我只需创建List<Type>
并用类型填充它。因此,让我们尝试提出与.NET框架相关的原因,而不是盲目地将C ++编码技术转换为C#。
P.P.S。
当然,这个讨论严格来说是学术性的。即使有问题的一百个令人信服的理由浮出水面,它也不会被实施。
答案 0 :(得分:7)
我偶尔偶然发现一个实施问题,我非常遗憾C<T>
无法继承T
。不幸的是,我从来没有记录过这些问题,因此我可以描述最近的问题 - 我现在偶然发现的问题。所以这就是:
我们的系统可通过元数据进行扩展,这些元数据在运行时可用。元数据将转换为使用Reflection.Emit在运行时动态生成的类型。不幸的是,动态生成的类型必须满足以下条件:
IDynamicObject
(我们的界面),System.ComponentModel.INotifyPropertyChanged
和Csla.Core.IMobileObject
。请注意,此条件会对祖先类型设置某些约束。例如,祖先类型可能不实现IDynamicObject
接口,除非所有接口方法都是抽象实现的。还有其他限制,必须检查所有限制。object
方法Equals
,ToString
和GetHashCode
。目前,我不得不使用Reflection.Emit发出所有IL代码来满足这些条件。当然,某些任务可能会转发到静态编译的代码。例如,object.Equals(object)
方法的覆盖调用DynamicObjectHelper(IDynamicObject, IDynamicObject)
,这是一个静态编译的C#代码,执行最大的工作量
比较两个动态对象的相等性。但这更像是一个例外而不是规则 - 大多数实现都被发出,这是一个痛苦的屁股。
能够编写泛型类型,如DynamicObjectBase<T>
,它将用祖先类型实例化并作为动态生成类型的实际基类型,这不是很好吗?在我看来,泛型类型DynamicObjectBase<T>
可以在静态编译的C#代码中实现大部分动态类型需求。动态发出的类型将继承它,并且可能只是覆盖一些简单的虚拟方法。
总而言之,令人信服的理由是,让C<T>
继承T
会大大简化发布动态类型的任务。
答案 1 :(得分:1)
我正在使用GTK#3。有一个小工具&#39; class defined,它是所有其他GTK#小部件的基础,例如窗口,标签,框架。不幸的是,框架使得更改窗口小部件的背景颜色变得有点复杂,因为您需要覆盖Widget的方法来执行此操作(jeez ...)。
所以,如果我想 - 比如说 - 我可以任意设置背景颜色的标签,我应该这样做:
class BGLabel : Label
{
private Color _bgColor;
public Color BackgroundColor
{
get { return this._bgColor; }
set { this._bgColor = value; this.QueueDraw(); /* triggers OnDraw */ }
}
protected override void OnDraw(...)
{
... /* here we can use _bgColor to paint the background */
}
}
现在,如果我想为更多小部件提供这个不错的功能,我必须为每个执行上述操作。如果&#34; C类&lt; T> :T&#34;可以编译,我可以改为:
class C<T> : T where T : Widget
{
private Color _bgColor;
public Color BackgroundColor
{
get { return this._bgColor; }
set { this._bgColor = value; this.QueueDraw(); /* triggers OnDraw */ }
}
protected override void OnDraw(...)
{
... /* here we can use _bgColor to paint the background */
}
}
而不是&#34; BGxxx bgw = new BGxxx();&#34;我可以使用&#34; C&lt; xxx&gt; bgw =新C&lt; xxx&gt;();&#34;。
答案 2 :(得分:1)
我曾经希望有一个数据库对象(例如MyEntity
)和一个相应的历史对象(例如MyEntityHistory
)。这些实体共享完全相同的属性,只是历史记录中仅存在两个属性:ValidFrom
和ValidTo
。
因此,为了显示对象的当前状态以及历史记录数据,我可以在它们之间获取这段文字UNION
,并使用Dapper或EF Core的{strong>映射功能 {1}}以将结果映射到FromSql
的历史记录。
当然,我想避免重复属性,因此理想情况下,我将继承实体类以获取其属性。而且,我想从其他一些基类中获取List<MyEntityHistory>
和ValidFrom
属性,因此最终遇到了钻石问题。会有帮助的是,我将定义一个这样的历史记录类:
ValidTo
class MyEntityHistory : HistoryEntity<MyEntity>
的定义如下:
HistoryEntity
不幸的是,class HistoryEntity<TEntity> : TEntity where TEntity: class
{
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
}
的这种实现是不可能的。
请注意,通过组合结构嵌入实体类(HistoryEntity<TEntity>
)是不可行的,因为Dapper或EF Core的映射器将无法处理嵌套对象。
答案 3 :(得分:0)
虽然我看到了你所看到的内容,但它看起来像一个特殊情况,通过.NET中的组合对类型构造的支持不足这一更普遍的问题。像https://connect.microsoft.com/VisualStudio/feedback/details/526307/add-automatic-generation-of-interface-implementation-via-implementing-member所述的方法是否适合你?
答案 4 :(得分:0)
防范这种情况的泛型的基本规则是“泛型类型的内容必须在泛型参数上明确定义”。让我们看看它如何适用于以下代码:
public abstract class AbstractBase
{
public abstract string MyMethod();
}
public class SomeType<T> : T
{
}
public class SomeUsage
{
void Foo()
{
// SomeType<AbstractBase> does not implement AbstractBase.MyMethod
SomeType<AbstractBase> b = new SomeType<AbstractBase>();
}
}
因此我们尝试实施MyMethod()
:
public class SomeType<T> : T
{
public override string MyMethod()
{
return "Some return value";
}
}
public class SomeUsage
{
void Foo()
{
// SomeType<string> does not inherit a virtual method MyMethod()
SomeType<string> b = new SomeType<string>();
}
}
因此,我们要求T
来自AbstractBase
:
public abstract class DerivedAbstractBase : AbstractBase
{
public abstract string AnotherMethod();
}
public class SomeType<T> : T
where T : AbstractBase
{
public override string MyMethod()
{
return "Some return value";
}
}
public class SomeUsage
{
void Foo()
{
// SomeType<DerivedAbstractBase> does not implement DerivedAbstractBase.AnotherMethod()
SomeType<DerivedAbstractBase> b = new SomeType<DerivedAbstractBase>();
}
}
当你考虑基类型中的所有限制时,你受到限制,从泛型参数派生是没有意义的。
答案 5 :(得分:0)
我将尝试用简单的例子来解释为什么我们需要从泛型类型继承。
简单的动机是:更容易开发我称之为编译时间顺序执行的东西,这在ORM框架中非常流行。
假设我们正在构建数据库框架。
以下是如何使用这样的框架构建事务的示例:
public ITransaction BuildTransaction(ITransactionBuilder builder)
{
/* Prepare the transaction which will update specific columns in 2 rows of table1, and one row in table2 */
ITransaction transaction = builder
.UpdateTable("table1")
.Row(12)
.Column("c1", 128)
.Column("c2", 256)
.Row(45)
.Column("c2", 512)
.UpdateTable("table2")
.Row(33)
.Column("c3", "String")
.GetTransaction();
return transaction;
}
由于每一行返回一些接口,我们希望以这样的方式返回它们,开发人员不能在操作顺序中出错,并且在编译时强制使用有效用法,这也简化了实现和使用TransactionBuilder,因为开发人员不会犯错误,如:
{
ITransaction transaction = builder
.UpdateTable("table1")
.UpdateTable("table2") /*INVALID ORDER: Table can't come after Table, because at least one Row should be set for previous Table */
}
// OR
{
ITransaction transaction = builder
.UpdateTable("table1")
.Row(12)
.Row(45) /* INVALID ORDER: Row can't come after Row, because at least one column should be set for previous row */
}
现在让我们看看今天的ITransactionBuilder接口,没有继承自generic,它将在编译时强制执行所需的顺序:
interface ITransactionBuilder
{
IRowBuilder UpdateTable(string tableName);
}
interface IRowBuilder
{
IFirstColumnBuilder Row(long rowid);
}
interface IFirstColumnBuilder
{
INextColumnBuilder Column(string columnName, Object columnValue);
}
interface INextColumnBuilder : ITransactionBuilder, IRowBuilder, ITransactionHolder
{
INextColumnBuilder Column(string columnName, Object columnValue);
}
interface ITransactionHolder
{
ITransaction GetTransaction();
}
interface ITransaction
{
void Execute();
}
正如您所看到的,我们为Column builder“IFirstColumnBuilder”和“INextColumnBuilder”提供了2个接口,这些接口实际上并不是必需的,并且记住这是编译时状态机的一个非常简单的例子,而在更复杂的问题中,不必要的接口将大幅增长。
现在让我们假设我们可以继承泛型并准备这样的接口
interface Join<T1, T2> : T1, T2 {}
interface Join<T1, T2, T3> : T1, T2, T3 {}
interface Join<T1, T2, T3, T4> : T1, T2, T3, T4 {} //we use only this one in example
然后,我们可以将界面重写为更直观的样式并使用单列构建器,而不会影响订单
interface ITransactionBuilder
{
IRowBuilder UpdateTable(string tableName);
}
interface IRowBuilder
{
IColumnBuilder Row(long rowid);
}
interface IColumnBuilder
{
Join<IColumnBuilder, IRowBuilder, ITransactionBuilder, ITransactionHolder> Column(string columnName, Object columnValue);
}
interface ITransactionHolder
{
ITransaction GetTransaction();
}
interface ITransaction
{
void Execute();
}
所以我们使用了Join&lt; ...&gt;组合现有接口(或“后续步骤”),这在状态机开发中非常有用。
当然,这个特定的问题可以通过在C#中添加“加入”接口的可能性来解决,但很明显,如果可以继承泛型,问题根本不存在,而且很明显编译时间执行是非常有用的事情。
顺便说一句。对于像
这样的语法 interface IInterface<T> : T {}
除了继承循环之外,没有任何“假设”情况,可以在编译时检测到。
我认为至少接口这个功能是100%需要的
此致
答案 6 :(得分:0)
我写了CodeFirstWebFramework,这是一个dll,提供编写数据库驱动的Web服务器的所有工具,表格自动从代码中的类生成,并将URL粘贴到AppModule类中的方法调用。
DLL提供了一些基本的AppModule类 - AdminModule和FileSender。这些实现管理(登录,维护用户,更新设置),以及当url与任何其他AppModule无关时返回文件。
DLL的消费者可以覆盖抽象的AppModule类,为所有自己的AppModule添加附加功能(将从覆盖中派生出来)。
我还希望他们能够覆盖AdminModule和/或FileSender。然而,它们能够覆盖这些模块中的一个(因此可以访问其中的默认代码),同时也是它们自己的AppModule实现的子类,这将是有用的(并且在某些情况下是必要的)。
我以为我有一个很棒的主意,如下:
在我的DLL中:
abstract class AppModule {
// Contains base class implementation, including virtual Database property, code to
// retrieve files, code to call appropriate method depending on url, code to collect
// result and return it to the web browser, etc.
}
abstract class AdminModule<T> where T:AppModule, new() : T {
// Contains implementation of Admin methods - create/edit users,
// edit settings, backup database, etc.
}
class AdminModule : AdminModule<AppModule> {
// No code needed - inherits implementation from AdminModule<AppModule>
}
在消费者计划(不同的名称空间)中:
abstract class AppModule : AppModule {
// Contains additional properties and methods. Also overides some virtual methods,
// e.g. returns a subclass of Database with additional methods, or retrieve files
// from the database instead of the file system.
}
class AdminModule : AdminModule<AppModule> {
// Additional code for Admin methods specific to this application
// Does not need code for methods in base class - inherits implementation from
// AdminModule<AppModule>
}
在这个特定的例子中,由于T是AppModule的约束,并且因为AdminModule&lt; T&gt;这本身就是抽象的,我无法理解为什么到目前为止提出的任何异议都适用。
编译器可以检查派生类中是否实现了任何抽象方法。构建AdminModule&lt; T&gt;没有危险,因为它是抽象的。
编写AdminModule&lt; T&gt;是编译时错误。如果T是密封类,或者是从AdminModule&lt; T&gt;派生的,或者其可访问性低于AdminModule&lt; T&gt;。
答案 7 :(得分:0)
之所以能够从类型参数中继承,是因为以下情况:
我想拥有一个抽象的ViewModel类,该类继承自Entity类,并且具有一些其他属性和方法。
public abstract class ViewModel<TEntity> : TEntity, IViewModel<TEntity>
where TEntity : class, IEntity, new() {
public void SomeMethod() {
}
}
然后我可以创建一个特定的ViewModel
public class EmployeeViewModel : ViewModel<Employee> {
}
好!它继承了实体的所有字段,并具有抽象viewmodel的标准属性和方法! ..
可悲的是现在无法完成。