Generic constraint specifying concrete/non-abstract type #1004
Replies: 11 comments
-
What is the point? What kind of design can work with any concrete type, but disallows all abstract types? Also, such generic constraint is not supported by the CLR, so this would likely require CLR changes. |
Beta Was this translation helpful? Give feedback.
-
Also a dupe of #742, and look where that discussion went. |
Beta Was this translation helpful? Give feedback.
-
You can ask the same question of the new() constraint as it exists today, since it requires that the generic argument type not be abstract, in addition to requiring it to have a default public constructor (an abstract type can have a default public constructor). To specifically answer your question, any design that must construct instances of the generic argument type, without having to specify exactly how it goes about doing that, or requiring them to have a specific constructor. |
Beta Was this translation helpful? Give feedback.
-
I will add, however, there's a potential to implement this as a bonus if Default Type Parameters (#278) could ever become a thing. |
Beta Was this translation helpful? Give feedback.
-
I don't really know if this is a good example, but since an example should be included here, I will post this, as I have experience with this particular aspect of the need for this feature: public abstract class Table : IEnumerable
{
/// implementations not shown.
public IEnumerator GetEnumerator()
{
/// implementations not shown.
}
}
public abstract class TableEntry
{
public string Name {get;set;}
/// balance of implementation not shown
}
public class PlanetTableEntry : TableEntry
{
/// implementations not shown.
}
public class PlanetTable : Table
{
/// implementations not shown.
}
public class AsteriodTableEntry : TableEntry
{
/// implementations not shown.
}
public class AsteriodTable : Table
{
/// implementations not shown.
}
public class CometTableEntry : TableEntry
{
/// implementations not shown.
}
public class CometTable : Table
{
/// implementations not shown.
}
/// All of the above classes are existing types
/// that must be used as-is, and the following
/// is part of a client:
public static class Helpers
{
public static IEnumerable<TEntry> GetTableEntries<TEntry>()
where TEntry : TableEntry //, new() error: there are no public constructors
{
return GetTableFromEntryType(typeof(TEntry)).Cast<TEntry>();
}
/// Implementation detail, gets the corresponding
/// table containing the given type:
public static Table GetTableFromEntryType(Type entryType)
{
/// implementations not shown.
}
}
public static class Test
{
public static void ValidUsage()
{
foreach(TableEntry planet in Helpers.GetTableEntries<PlanetTableEntry>())
{
Console.WriteLine("Planet name = {0}", planet.Name);
}
}
public static void ErroneousUsage()
{
/// The caller erroneously specifies the abstract base type
/// where a concrete derived type is required. The new()
/// constraint cannot be used on GetTableEntries() because
/// the types it enumerates have no public constructors.
foreach(TableEntry asteroid in Helpers.GetTableEntries<TableEntry>())
{
Console.WriteLine("Asteroid name = {0}", asteroid.Name);
}
}
} |
Beta Was this translation helpful? Give feedback.
-
It has to require a non-abstract type, so that you can write
Why would you not want to specify how the instances are constructed? The design likely has other restrictions about what types are allowed, so requiring just that the type has to be concrete is a half-measure.
Similar as above, the |
Beta Was this translation helpful? Give feedback.
-
Before I read this, I read the code several times and failed to spot anything new. This is not a good sign. |
Beta Was this translation helpful? Give feedback.
-
@ActivistInvestor Your entire goal is to prevent calls like this: Helpers.GetTableEntries<TableEntry>() I'm sympathetic because I've written similar kinds of method, where the actual construction is magically done by Entity Framework or Json.NET or some such thing. However, that's not the only hole. What if I create The alternative is to throw for invalid generic type arguments just like you do for invalid method arguments. If you're used to throwing Also, unlike |
Beta Was this translation helpful? Give feedback.
-
The intended constraint is not 'a constraint that would require any type derived from TableEntry, but disallow TableEntry itself', because there could be any number of intermediate, derived abstract types between a TableEntry and a concrete derived type.
See my revision of the example code. Concrete TableEntry-based types are sealed, and both TableEntry and Table (and all derived, intermediate abstract types) have no client-accessible constructors, making it impossible for a client to derive from either type. The intended constraint is not nearly as complicated as you make it seem. It is very simply any concrete type derived from TableEntry, whether there is a corresponding Table-based type for it, or not, and in that case, the server that provides TableEntry, Table, and all derived types is ultimately responsible for ensuring there is a corresponding Table-based type for a given TableEntry-based type. The constraint is intended to prevent a client from erroneously specifying an abstract base of a concrete TableEntry type as the generic argument. Because concrete TableEntry based types are sealed, and both the abstract Table and TableEntry types have no constructors that are accessible to a client, a client cannot derive from either of same, sealed or not. What you refer to as the 'real constraint' is impossible since that implies a search for types that may, or may not exist at compile time, but could exist at runtime, and that's also why an analyzer would not be a solution either. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour We can always use reflection internally for many kind of function such as serialization. This feature would bring the error that could cause by trying to deserialize abstract class at compile time |
Beta Was this translation helpful? Give feedback.
-
I do support this proposal. However, now that this is accepted, I think the best and most useful case is this: public TDel CreateDelegate<TDel>() where TDel : Delegate, !abstract //my preferred syntax Writing your own analyzer is cool, but when you are writing a library, the best thing is to use language features to express the intent and limitations of the API rather than custom attributes or comments. In the case of |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
In many of the use-cases I've had involving generic types that are abstract, the pattern requires that the type of the generic argument be a non-abstract or concrete type. The new() constructor can enforce that only if the generic argument type provides a public default constructor, which may not always be the case. What I propose is a way to enforce the constraint that a generic argument type be non-abstract, without regards for what constructors it provides:
In the example, the new keyword is used without trailing parenthesis, and serves as a way to assert that T must not be abstract, without regards for what constructors it has or doesn't have.
This design would also support the proposed parameterized new constraint as well, and could also be viewed as merely an extension of that.
Beta Was this translation helpful? Give feedback.
All reactions