问题
I need to inherit a collection of items of the current type, like this
class A {
// some properties...
public ICollection<A> Children;
}
class B: A {
// other properties
}
This mostly works as expected. The problem is I can do something like this
class C: A { }
B b = new B();
b.Children = new List<C>();
Is there any way to force b.Children
to be a collection of B
?
回答1:
No, there is no way to do such thing yet.
The C# language has no artifact to declare such thing:
class A { public ICollection<T> Children where T : thisdeclaring; }
Where
thisdeclaring
represents the current declaring type.C# does not support true polymorphism on open types using the diamond operator
<>
How to create List of open generic type of class?
How to do generic polymorphism on open types in C#?
Solution 1 : type checking hack on the non generic type
We check the type at runtime to throw an exeption in case of mismatch, but we must lost the genericity as explained in previous links:
using System.Reflexion;
class A
{
private ICollection _Children;
public ICollection Children
{
get => _Children;
set
{
if ( value == null )
{
_Children = null;
return;
}
var genargs = value.GetType().GenericTypeArguments;
if (genargs.Length != 1 || this.GetType() != genargs[0] )
{
string msg = $"Type of new {nameof(Children)} items must be {this.GetType().Name}: "
+ $"{ genargs[0].Name} provided.";
throw new TypeAccessException(msg);
}
_Children = value;
}
}
}
Test
var a = new A();
trycatch(() => a.Children = new List<A>());
trycatch(() => a.Children = new List<B>());
Console.WriteLine();
var b = new B();
trycatch(() => b.Children = new List<A>());
trycatch(() => b.Children = new List<B>());
void trycatch(Action action)
{
try
{
action();
Console.WriteLine("ok");
}
catch ( Exception ex )
{
Console.WriteLine(ex.Message);
}
}
Output
ok
Type of new Children items must be A: B provided.
Type of new Children items must be B: A provided.
ok
So we can't have the generic type parameter on the collection and the hierarchical type contrainst at the same time, as I know, for the moment.
Solution 2 : the same hack using dynamic to keep genericity
private dynamic _Children;
public dynamic Children
set
{
if ( value == null )
{
_Children = null;
return;
}
bool isValid = false;
foreach ( Type type in value.GetType().GetInterfaces() )
if ( type.IsGenericType )
if ( type.GetGenericTypeDefinition() == typeof(ICollection<>) )
{
isValid = true;
break;
}
if ( !isValid )
{
string msg = $"{nameof(Children)} must be a ICollection of {this.GetType().Name}: "
+ $"{value.GetType().Name} provided.";
throw new TypeAccessException(msg);
}
var genargs = value.GetType().GenericTypeArguments;
if ( genargs.Length != 1 || this.GetType() != genargs[0] )
{
string msg = $"Type of new {nameof(Children)} items must be {this.GetType().Name}: "
+ $"{ genargs[0].Name} provided.";
throw new TypeAccessException(msg);
}
_Children = value;
}
}
Here we keep the generic closed constructed type of the collection.
Thus we can use all generic members of the stored instance.
回答2:
Yes, you can do it, but with a caveat.
You define the class like this:
public class A<T> where T : A<T>
{
public ICollection<T> Children;
}
Now you can inherit it to make the class you're looking for:
public class B : A<B>
{ }
This allows this code to work:
B b = new B();
ICollection<B> children = b.Children;
The caveat is that the language doesn't enforce you to do the right thing.
You could, instead, do this:
public class C : A<B>
{ }
That is legal but breaks the contract you're looking for. So it just becomes an exercise in making sure you implement your classes correctly.
来源:https://stackoverflow.com/questions/65605045/how-to-define-an-aggregated-icollectiont-where-t-is-type-of-the-current-declar