首页 > 解决方案 > 如何定义聚合的 ICollection其中 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

标签: c#genericsinheritancecollectionsgeneric-constraints

解决方案


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

解决方案 1:对非泛型类型进行类型检查

我们在运行时检查类型以在不匹配的情况下抛出异常,但我们必须丢失前面链接中解释的通用性:

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;
  }
}

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

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


推荐阅读