What are the good reasons to wish that .NET generics could inherit one of the generic parameter types?
This post is in continuation of this one.
I am trying to understand if I am the only one who misses and needs the ability of a .NET generic type to inherit one of its generic parameter types.
The challenge is to collect compelling reasons in favour of this feature or, alternatively, get to know that there are none.
I give my reason to have it as an answer to this question - see below.
I am asking folks out there to add theirs as answers to this post.
If you disagree that the feature is useful or have no good reasons in favour - please refrain from posting anything here, though you can do so in the original post that has started it all - here.
P.S.
Some C++ patterns are irrelevant in .NET. For instance, in his excellent book Modern C++ Design Andrei Alexandrescu describes how to create a list of types evaluated at the compile time. Naturally, this pattern is irrelevant for .NET, where 开发者_JS百科if I need a list of types I just create List<Type>
and populate it with types. So, let us try to come up with reasons pertinent to the .NET framework and not just blindly translating C++ coding techniques to C#.
P.P.S.
Of course, this discussion is strictly academic. Even if a hundred compelling reasons for the feature in question is surfaced it is not going to be implemented, ever.
Every now and then I stumble upon an implementation issue, where I deeply regret that C<T>
cannot inherit T
. Unfortunately, I have never recorded these issues and so I can describe just the most recent one - the one I have stumbled upon right now. So here it is:
Our system is extensible through metadata, which becomes available at run-time. The metadata is translated to a type dynamically generated at run-time using Reflection.Emit. Unfortunately, the dynamically generated type has to satisfy the following conditions:
- It must derive from some other type, provided as a parameter to the dynamic type creator. This type is called the ancestor type and is always a statically compiled type.
- It must implement several interfaces, say
IDynamicObject
(our interface),System.ComponentModel.INotifyPropertyChanged
andCsla.Core.IMobileObject
. Note, that this condition places certain constraints on the ancestor type. For instance, the ancestor type may not implement theIDynamicObject
interface, except if all the interface methods are implemented abstractly. There are other constraints, all of which must be checked. - It should (and it does) override the
object
methodsEquals
,ToString
andGetHashCode
.
Currently, I had to use Reflection.Emit to emit all the IL code to satisfy these conditions. Of course, some tasks may be forwarded to statically compiled code. For instance, the override of the object.Equals(object)
method invokes DynamicObjectHelper(IDynamicObject, IDynamicObject)
which is a statically compiled C# code doing the largest bulk of the work
of comparing two dynamic objects for equality. But this is more of an exception than the rule - most of the implementation is emitted, which is a pain in the butt.
Would not it be great to be able to write a generic type, like DynamicObjectBase<T>
which will be instantiated with the ancestor type and serve as the actual base type of the dynamically generated type? As I view it, the generic type DynamicObjectBase<T>
could implement much of the dynamic type requirements in a statically compiled C# code. The dynamically emitted type would inherit it and probably, just override a few simple virtual methods.
To conclude, my compelling reason is that letting C<T>
inherit from T
would greatly simplify the task of emitting dynamic types.
I'm using GTK# 3. There's a 'Widget' class defined which is the base for all other GTK# widgets, e.g. Window, Label, Frame. Unfortunately, the framework makes it a bit complicated to change the background-color of a widget, in the sense that you need to override a method of the Widget to do it (jeez...).
So, if I wanted to have - say - a Label for which I can arbitrarily set a background-color, I should do something like this:
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 */
}
}
Now, if I wanted to have this nice feature for more widgets, I'd have to do the above for each of them. If "class C< T > : T" would be compilable, I could instead just do:
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 */
}
}
and instead of "BGxxx bgw = new BGxxx();" I could use "C< xxx > bgw = new C< xxx >();".
Once when I wanted this was when I had database objects (e.g. MyEntity
) that also had a corresponding history object (e.g. MyEntityHistory
). These entities shared exactly the same properties, except for 2 that were only present on the history one: ValidFrom
and ValidTo
.
So, in order to display the current state of an object as well as history data, I could fetch this writing UNION
between them and use mapping capabilities of Dapper or EF Core's FromSql
to map the result to List<MyEntityHistory>
history.
Of course I would like to avoid duplicating the properties, so Ideally I'd inherit the entity class to get it's properties. But also, I'd like to get the ValidFrom
and ValidTo
properties from some other base class, so I'd end up with a diamond problem. What would help, is that I'd have a history class defined like this:
class MyEntityHistory : HistoryEntity<MyEntity>
Where HistoryEntity
would be defined as such:
class HistoryEntity<TEntity> : TEntity where TEntity: class
{
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
}
Unfortunately such implementation of HistoryEntity<TEntity>
is not possible.
Note that embedding entity class (MyEntity
) through composition is not an option, because Dapper or EF Core's mapper wouldn't be able to work around the nested object.
While I see what you're getting at, it looks like a special case of a more general problem of poor support for type construction via composition in .NET. Would something like the approach described at https://connect.microsoft.com/VisualStudio/feedback/details/526307/add-automatic-generation-of-interface-implementation-via-implementing-member be sufficient for you?
The basic rule of generics that prevents this is "the content of a generic type must be well-defined on the generic argument." Lets look at how this applies to the following code:
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>();
}
}
So we try to implement 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>();
}
}
So lets make a requirement that T
be derived from 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>();
}
}
Summary:
By the time you account for all the restrictions in base types, you're so constrained that deriving from a generic parameter is pointless.
I will try to explain with simple example why do we need the inheritance from generic types.
The motivation in short: is to make much easier the development of something that I call Compile Time Order Enforcement, which is very popular in ORM Frameworks.
Assume that we are building Database Framework.
Here is the example how to build the transaction with such a framework:
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;
}
Since every line returns some interface, we would like to return them such way, that developer can't make a mistake in operation order, and valid usage will be enforced at compile time, which also makes simplier the implementation and usage of TransactionBuilder, because developer just won't be able to make mistakes like:
{
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 */
}
Now let's look on the ITransactionBuilder interface today, WITHOUT inheritance from generic, which will enforce the desired order at compile time:
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();
}
As you can see we have 2 interfaces for Column builder "IFirstColumnBuilder" and "INextColumnBuilder" which actualy are not neccessary, and remember that this is very simple example of compile time state machine, while in more complex problem, the number of unneccessary interfaces will grow dramatically.
Now let's assume we can inherit from generics and prepared such interfaces
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
Then, we can rewrite our interfaces to more intuitive style and with single column builder, and without affecting the order
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();
}
So we used Join<...> to combine existing interfaces(or the "next steps"), which is very usefull in state machine development.
Ofcourse, this specific problem may be solved by adding possibility in C# for "joining" interfaces, but it's clear that if it was possible to inherit from generics, the problem was not exist at all, as well as it's clear that compile time order enforcement is very usefull thing.
BTW. For syntax like
interface IInterface<T> : T {}
There are no any "what if" cases except inheritance loop, which may be detected at compile time.
I think that AT LEAST for interfaces this feature is 100% needed
Regards
I have written CodeFirstWebFramework, which is a dll providing all the tools to write a database driven web server, with the tables automatically generated from classes in the code, and gluing urls to calls to methods in AppModule classes.
The DLL provides some base AppModule classes - AdminModule and FileSender. These implement administration (logging on, maintaining users, updating settings), and returning files when the url does not relate to any other AppModule.
Consumers of the DLL can override the abstract AppModule class to add additional functionality to all their own AppModules (which will be derived from the override).
I would also like them to be able to override AdminModule and/or FileSender. However it would be useful (and, in some cases, essential) for them to be able to override one of these modules (thus having access to the default code therein), while also subclassing their own AppModule implementation.
I thought I had had a brilliant idea, as follows:
In my 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>
}
In the consumer program (different Namespace):
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>
}
In this particular instance, because of the constraint that T is an AppModule, and because AdminModule<T> is itself abstract, I can't see any reason why any of the objections raised so far would apply.
The compiler could check that any abstract methods were implemented in derived classes. There would be no danger of constructing an AdminModule<T>, because it is abstract.
It would be a compile-time error to write AdminModule<T> if T was a sealed class, or derived from AdminModule<T>, or has less accessibility than AdminModule<T>.
The good reason to make it possible to inherit from a type parameter is because of the following situation:
I have would like to have an abstract ViewModel class which inherits from an Entity class and has some additional properties and methods.
public abstract class ViewModel<TEntity> : TEntity, IViewModel<TEntity>
where TEntity : class, IEntity, new() {
public void SomeMethod() {
}
}
Then i could make a specific ViewModel
public class EmployeeViewModel : ViewModel<Employee> {
}
Nice ! it inherits all the fields of the entity and has the standard properties and methods of the abstract viewmodel ! ..
This sadly cannot be done now.
精彩评论