4

Suppose I have a basic inheritance structure:

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

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

I want to define an array of Types such that only Types which inherit from Letter are valid. So a sample array might look like:

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

But the assignment

myArray[0] = typeof(One);

would fail. Is it possible to define such a structure?

Dan Brenner
  • 880
  • 10
  • 23

3 Answers3

3

All types are instances of System.Type, being the same statically. Therefore, doing this using a conventional array is not possible. One option is to create a data structure that only allows addition through type parameters:

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

This would allow:

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

Note: I'm using a ISet<T> as the underlying data structure, you may or may not want to use a List<T> instead depending on the requirements.

Bas
  • 26,772
  • 8
  • 53
  • 86
1

I don't think it is possible that you can use the type system to enforce this: typeof returns an instance of System.Type. This is a generic class and there is no aspect of the typesystem that can enforce that only subtypes of a certain type can be stored. So using the typesystem for this is probably not the way to go.

There are some alternatives however:

  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
        //...
    
    }
    
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
0

You can always define an array of Letter, and only Letter objects and types derived from it can be assigned to the elements. However, when you get an element of the array, the compiler will think it is a Letter. That may be what you want. If not, you can discover what it really is at run-time:

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

The is and as operators are used in these downcasts to verify that the downcast is valid.

Or, better, you can define behavioral properties and methods of your Letter base class that allow the caller to obtain the behaviors of the derived classes without having to know what the exact class of the object is.

myArray[i].DrawSelf(Point ptStart)

and make the various derived classes responsible for knowing how to draw themselves starting at a given location.

S. Rojak
  • 454
  • 2
  • 6
  • 3
    I think you do not understand the problem correctly. `myArray` will contain `System.Type` **instances**, **not** instances of `Letter`. – Willem Van Onsem Jan 11 '16 at 23:15
  • Wrap your array in accessors that verify the `Type` instance meets the test `typeof(Letter).IsAssignableFrom(t)`. – S. Rojak Jan 11 '16 at 23:21