How to define an aggregated ICollection<T> where T is type of the current declaring class within a hierarchy?

别说谁变了你拦得住时间么 提交于 2021-02-19 12:48:31

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!