将类型定义为类型的超集

时间:2016-01-11 22:59:46

标签: c# arrays types

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

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

public class Number {...} 
public class One : Number {...}

我想定义一个Type数组,只有从Type继承的Letter才有效。因此,示例数组可能如下所示:

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

但是作业

myArray[0] = typeof(One);

会失败。是否可以定义这样的结构?

3 个答案:

答案 0 :(得分:3)

所有类型都是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>,而不是取决于要求。

答案 1 :(得分:1)

我认为你不可能使用类型系统来强制执行此操作:typeof返回System.Type的实例。这是一个泛型类,并且类型系统没有任何方面可以强制只能存储某种类型的子类型。所以使用类型系统可能不是这样的。

但是有一些替代方案:

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

    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
        //...
    
    }
    

答案 2 :(得分:0)

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

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

在这些向下广播中使用isas运算符来验证向下转发是否有效。

或者,更好的是,您可以定义Letter基类的行为属性和方法,以允许调用者获取派生类的行为,而无需知道对象的确切类是什么。

myArray[i].DrawSelf(Point ptStart)

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