如何定义聚合的 ICollection<T>,其中 T 是层次结构中当前声明 class 的类型?
How to define an aggregated ICollection<T> where T is type of the current declaring class within a hierarchy?
我需要继承当前类型的项目集合,像这样
class A {
// some properties...
public ICollection<A> Children;
}
class B: A {
// other properties
}
这基本上按预期工作。问题是我可以做这样的事情
class C: A { }
B b = new B();
b.Children = new List<C>();
有没有办法强制b.Children
成为B
的集合?
不,还没有办法做这样的事情。
C#语言没有神器可以声明这样的东西:
class A
{
public ICollection<T> Children where T : thisdeclaring;
}
其中thisdeclaring
表示当前声明类型。
C# 不支持使用菱形运算符的开放类型的真正多态性 <>
解决方案 1:对非通用类型进行类型检查 hack
我们在运行时检查类型以在不匹配的情况下抛出异常,但我们必须失去通用性,如先前链接中所述:
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;
}
}
}
测试
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);
}
}
输出
ok
Type of new Children items must be A: B provided.
Type of new Children items must be B: A provided.
ok
因此,据我所知,目前我们不能同时对集合使用泛型类型参数和对层次类型进行约束。
解决方案 2:同样的 hack 使用动态来保持通用性
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;
}
}
这里我们保留集合的通用封闭构造类型。
因此我们可以使用存储实例的所有通用成员。
是的,你可以做到,但有一个警告。
您这样定义 class:
public class A<T> where T : A<T>
{
public ICollection<T> Children;
}
现在您可以继承它来制作您正在寻找的class:
public class B : A<B>
{ }
这允许此代码工作:
B b = new B();
ICollection<B> children = b.Children;
需要注意的是,该语言不会强制您做正确的事。
您可以这样做:
public class C : A<B>
{ }
这是合法的,但违反了您正在寻找的合同。因此,这只是确保您正确实施 classes 的练习。
我需要继承当前类型的项目集合,像这样
class A {
// some properties...
public ICollection<A> Children;
}
class B: A {
// other properties
}
这基本上按预期工作。问题是我可以做这样的事情
class C: A { }
B b = new B();
b.Children = new List<C>();
有没有办法强制b.Children
成为B
的集合?
不,还没有办法做这样的事情。
C#语言没有神器可以声明这样的东西:
class A { public ICollection<T> Children where T : thisdeclaring; }
其中
thisdeclaring
表示当前声明类型。C# 不支持使用菱形运算符的开放类型的真正多态性
<>
解决方案 1:对非通用类型进行类型检查 hack
我们在运行时检查类型以在不匹配的情况下抛出异常,但我们必须失去通用性,如先前链接中所述:
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;
}
}
}
测试
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);
}
}
输出
ok
Type of new Children items must be A: B provided.
Type of new Children items must be B: A provided.
ok
因此,据我所知,目前我们不能同时对集合使用泛型类型参数和对层次类型进行约束。
解决方案 2:同样的 hack 使用动态来保持通用性
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;
}
}
这里我们保留集合的通用封闭构造类型。
因此我们可以使用存储实例的所有通用成员。
是的,你可以做到,但有一个警告。
您这样定义 class:
public class A<T> where T : A<T>
{
public ICollection<T> Children;
}
现在您可以继承它来制作您正在寻找的class:
public class B : A<B>
{ }
这允许此代码工作:
B b = new B();
ICollection<B> children = b.Children;
需要注意的是,该语言不会强制您做正确的事。
您可以这样做:
public class C : A<B>
{ }
这是合法的,但违反了您正在寻找的合同。因此,这只是确保您正确实施 classes 的练习。