Generic Type Parameter constraints in C# .NET

10,322

Solution 1

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) { /*...*/ }
    }
}

Solution 2

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

Solution 3

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.

Share:
10,322
Matthew Layton
Author by

Matthew Layton

Passionate about programming, technology and DLT.

Updated on July 23, 2022

Comments

  • Matthew Layton
    Matthew Layton almost 2 years

    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

  • Matthew Layton
    Matthew Layton over 11 years
    Thanks for your answer...I had a little play myself. Please take a look at my answer if you have a minute and see what you think. Any constructive criticism is appreciated! :-)
  • Matthew Layton
    Matthew Layton over 11 years
    " 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." - agreed, string on its own is pointless, but lets say I wanted to constrain to String, StringBuilder and SecureString. This would be more useful, but still illegal becuase string is sealed :-(
  • Marc Gravell
    Marc Gravell over 11 years
    @activwerx that is not something supported by generics.
  • nawfal
    nawfal over 11 years
    But there are no workarounds at times when our choices are constrained by C# and .NET's design principles.
  • Matthew Layton
    Matthew Layton over 11 years
    I see. Maybe if I shed some light onto the situation, that may help. Say I am designing an object called Element (as in HTML / XML element). the element is essentially a Key/Value pair. Key is always of type string, however value can be of type string, or Element (because elements can be nested). So what I need is a mechanism which constrains the generic type parameter for value to only string, or Element (and possibly IEnumerable<Element> for arrays, lists etc). Sorry this probably seems like a very convoluted way of implementing such an object!
  • Matthew Layton
    Matthew Layton over 11 years
    @nawfal, can you elaborate, please?
  • nawfal
    nawfal over 11 years
    @activwerx what I meant is exactly evident from your problem. You can not specify multiple type parameters in where as in int or byte or string. Also you cant do that for even one type since they all are sealed. So I mean these kinda hacks are the only possible solutions for things constrained either by the language or the framework.