Matthew Layton
Matthew Layton

Reputation: 42229

Generic Type Parameter constraints in C# .NET

Consider the following Generic class:

public class Custom<T> where T : string
{
}

This produces the following error:

'string' is not a valid constraint. A type used as a constraint must be an interface, a non-sealed class or a type parameter.

Is there another way to constrain which types my generic class can use?

Also, can I constrain to multiple types?

E.G.

T can only be string, int or byte

Upvotes: 10

Views: 12148

Answers (3)

Matthew Layton
Matthew Layton

Reputation: 42229

After reviewing the answers here, and having a little play around myself, I have come up with the following implementation, which checks the constraints at runtime rather than compile time.

// This example takes 3 parameters...
public class GenericConstraint<T1, T2, T3>
{
    public GenericConstraint(Type type)
    {
        if (!(type is T1) || !(type is T2) || !(type is T3))
        {
            throw new Exception("This is not a supported type");
        }
    }
}

Now I inherit this from my Custom class...

public class Custom<T> : GenericConstraint<string, int, byte>
{
    public Custom() : base(typeof(T))
    {
    }
}

This now throws an error:

Custom<long> item = new Custom<long>();

This does not!

Custom<byte> item2 = new Custom<byte>();

As stated by Marc Gravell, this is not a good use of Inheritance or Generics. Thinking about this logically, by inheriting GenericConstraint, this is limiting inheritance to this only, and also not using the type hierarchy properly. In terms of the use of generics, this is actually pretty pointless!

Therefore I have another solution which acts as a helper method to constrain the types at runtime. This frees up the object from inheritance and therefore has no effect on the type hierarchy.

public static void ConstrainParameterType(Type parameterType, GenericConstraint constraintType, params Type[] allowedTypes)
        {
            if (constraintType == GenericConstraint.ExactType)
            {
                if (!allowedTypes.Contains<Type>(parameterType))
                {
                    throw new Exception("A runtime constraint disallows use of type " + parameterType.Name + " with this parameter.");
                }
            }
            else
            {
                foreach (Type constraint in allowedTypes)
                {
                    if (!constraint.IsAssignableFrom(parameterType))
                    {
                        throw new Exception("A runtime constraint disallows use of type " + parameterType.Name + " with this parameter.");
                    }
                }
            }
        }

public enum GenericConstraint
    {
        /// <summary>
        /// The type must be exact.
        /// </summary>
        ExactType,

        /// <summary>
        /// The type must be assignable.
        /// </summary>
        AssignableType
    }

This now allows multiple type constraints on generic objects, even where types are sealed etc.

"public class Custom where T : string ... is not allowed, because the only T that meets that is: string (string is sealed) - making it rather pointless as a generic."

Yes, this is pointless, but in some circumstances, you might want to constrain an object to allow, for example; String, StringBuilder and SecureString. While this does not provide compile time constraining, it does provide runtime constraining, and some flexibility on which types can be used in the constraint.

Upvotes: 2

Radim K&#246;hler
Radim K&#246;hler

Reputation: 123851

From my experience I would say that I understand why you would like to have, string and int ... because of a generic base class having ID of type string or int

But it is for sure, that this is not possible. As this msdn description says: http://msdn.microsoft.com/en-us/library/d5x73970%28v=vs.80%29.aspx

We can have a constraing class (reference object like string) or struct (ValueType like int) So mixing string and int won't be possible

NOTE: the error for string make sense, because string is sealed, so it do not have to be as generic - string ID is what we need

Upvotes: 2

Marc Gravell
Marc Gravell

Reputation: 1062502

public class Custom<T> where T : string

is not allowed, because the only T that meets that is: string (string is sealed) - making it rather pointless as a generic.

Also, can I constrain to multiple types?

no - unless you do that at runtime via reflection rather than in a constraint (the static constructor is one way to do that - throwing an exception if used incorrectly)

T can only be string, int or byte

You might use something like IEquatable<T>, but that doesn't restrict it as much as you would like, so ultimately: no.

Something you could do is access it via an overloaded factory:

public abstract class Custom
{
    public static Custom Create(int value)
    { return new CustomImpl<int>(value); }
    public static Custom Create(byte value)
    { return new CustomImpl<byte>(value); }
    public static Custom Create(string value)
    { return new CustomImpl<string>(value); }
    private class CustomImpl<T> : Custom
    {
        public CustomImpl(T val) { /*...*/ }
    }
}

Upvotes: 20

Related Questions