如何定义聚合的 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 的练习。