定义一个类型作为其他类型的超集

4

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

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)[] myArray = new (type)[] {typeof(A), typeof(B), typeof(C)}; 但是,赋值操作 myArray[0] = typeof(One); 会失败。是否有可能定义这样的结构?

1
脑海中首先想到的是带有约束条件的泛型。 - Tim
@Tim:请注意,存储的不是对象,而是System.Type... - Willem Van Onsem
1
我认为.NET类型系统无法强制执行这一点。 - zneak
当然可以,因为你可以编写带有索引器的类,并在其中放置任何逻辑。但它不能是一个简单的数组。 - Jon
@Tim:你可以用Java做到这一点(尽管检查仍然可以被欺骗),但在C#中,“System.Type”不是泛型。 - Willem Van Onsem
显示剩余5条评论
3个回答

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
我认为使用类型系统来强制执行这一点是不可能的:typeof返回System.Type的实例。这是一个通用类,没有任何类型系统方面可以强制仅存储某种类型的子类型。因此,使用类型系统可能不是解决问题的方法。
然而,有一些替代方案:
  1. You could use contract contracts and hope that your can verify this at compile time. For instance:

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

    Note however that it is an undecidable problem, and that the contract verifier can only give a conservative answer.

  2. You could define a data-structure (probably a subset of an ICollection<Type> that enforces this at the front door. For instance:

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

0
你可以始终定义一个由 Letter 组成的数组,只有 Letter 对象和从它派生的类型才能被赋值给这些元素。然而,当你获取数组的元素时,编译器会认为它是一个 Letter。这可能是你想要的结果。如果不是,你可以在运行时发现它的真实类型:
if (myArray[i] is A)
{
    A objA = myArray[i] as A;
    ...

在这些向下转换中,isas 运算符用于验证向下转换是否有效。

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

myArray[i].DrawSelf(Point ptStart)

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


3
我认为你没有正确理解问题。myArray 将包含 System.Type实例,而不是 Letter 的实例。 - Willem Van Onsem
将您的数组包装在访问器中,以验证Type实例是否符合测试typeof(Letter).IsAssignableFrom(t) - S. Rojak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接