将类型定义为类型的超级集

本文关键字:类型 定义 | 更新日期: 2023-09-27 18:29:49

假设我有一个基本的继承结构:

public class Letter {...}
public class A : Letter {...}
public class B : Letter {...}
public class C : Letter {...}
public class Number {...} 
public class One : Number {...}

我想定义一个Type的数组,这样只有从Letter继承的Type才有效。因此,一个样本数组可能看起来像:

(type)[] myArray = new (type)[] {typeof(A), typeof(B), typeof(C)};

但是任务

myArray[0] = typeof(One);

将失败。有可能定义这样一个结构吗?

将类型定义为类型的超级集

所有类型都是System.Type的实例,静态相同。因此,使用传统阵列进行此操作是不可能的。一种选择是创建一个只允许通过类型参数添加的数据结构:

internal sealed class LetterTypeSet : IReadOnlyCollection<Type>
{
    readonly ISet<Type> types = new HashSet<Type>();
    public IEnumerator<Type> GetEnumerator() => this.types.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
    public int Count => this.types.Count;
    public bool Add<T>()
        where T : Letter
    {
        return this.types.Add(typeof (T));
    }
}

这将允许:

var letters = new LetterTypeSet();
letters.Add<A>(); //ok
letters.Add<B>(); //ok
letters.Add<One>(); //CS0311 "The type 'One' cannot be used ..."

注意:我使用ISet<T>作为底层数据结构,根据需要,您可能希望也可能不希望使用List<T>

我认为您不可能使用类型系统来强制执行这一点:typeof返回System.Type的实例。这是一个泛型类,类型系统的任何方面都不能强制要求只能存储特定类型的子类型。因此,使用类型系统可能不是解决问题的方法。

然而,也有一些替代方案:

  1. 您可以使用合同,并希望您可以在编译时验证这一点。例如:

    Type[] myArray = new (type)[] {typeof(A), typeof(B), typeof(C)};
    //...
    Contract.Ensures(Contract. ForAll (myArray,t => typeof(Letter).IsAassignableFrom(t));
    

    然而,请注意,这是一个无法确定的问题,合同验证器只能给出一个保守的答案。

  2. 你可以定义一个数据结构(可能是ICollection<Type>的一个子集,在前门强制执行

    public class SubTypeList<T> : IList<Type> {
        private readonly List<Type> innerList = new List<Type>();
        public void Add (Type type) {
            if(typeof(T).IsAssignableFrom(type)) {
                innerList.Add(type);
            } else {
                throw new ArgumentException("The given System.Type must be an inherit from T!");
            }
        }
        //Implement other methods
        //...
    }
    

您始终可以定义Letter的数组,并且只有Letter对象和从中派生的类型可以分配给元素。但是,当您获得数组的一个元素时,编译器会认为它是Letter。这可能就是你想要的。如果没有,您可以在运行时发现它的真实情况:

if (myArray[i] is A)
{
    A objA = myArray[i] as A;
    ...

在这些下变频中使用isas运算符来验证下变频是否有效。

或者,更好的是,您可以定义Letter基类的行为属性和方法,使调用者能够获得派生类的行为,而不必知道对象的确切类是什么。

myArray[i].DrawSelf(Point ptStart)

并使各种派生类负责知道如何从给定位置开始绘制自己。