如何定义聚合 ICollection<T> 其中 T 是层次结构中当前声明类的类型?

问题描述

我需要继承当前类型的项目集合,像这样

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 的集合?

解决方法

不,目前还没有办法做这样的事情。

解决方案 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;
  }
}

这里我们保留了集合的通用封闭构造类型。

因此我们可以使用存储实例的所有通用成员。

,

是的,你可以做到,但有一个警告。

您可以这样定义类:

public class A<T> where T : A<T>
{
    public ICollection<T> Children;
}

现在你可以继承它来创建你正在寻找的类:

public class B : A<B>
{ }

这允许此代码工作:

B b = new B();
ICollection<B> children = b.Children;

需要注意的是,该语言不会强制您做正确的事情。

你可以这样做:

public class C : A<B>
{ }

这是合法的,但违反了您正在寻找的合同。因此,它只是确保您正确实现类的练习。