C# Generic constraints to include value types AND strings

36,174

Solution 1

No, you can't. Generic constraints are always "AND"-ed, if you see what I mean (i.e. all constraints must be satisifed), so even if you were trying to use some unsealed class, this would still fail.

Why do you want to do this? Perhaps there's another approach which would work better.

Solution 2

Maybe you could restrict to IConvertible types? All the system primitives that can be converted using these interface methods also implement the interface, so this restriction would require T to be one of the following:

  • Boolean
  • Byte
  • Char
  • DateTime
  • Decimal
  • Double
  • Int (16, 32 and 64-bit)
  • SByte
  • Single (float)
  • String
  • UInt (16, 32 and 64-bit)

If you have an IConvertible, chances are VERY good it's one of these types, as the IConvertible interface is such a pain to implement that it's rarely done for third-party types.

The main drawback is that without actually converting T to an instance of one of these types, all your method will know how to do is call the Object and IConvertible methods, or methods that take an Object or IConvertible. If you need something more (like the ability to add and/or concatenate using +), I think that simply setting up two methods, one generic to struct types and a second strongly-typed to strings, would be the best bet overall.

Solution 3

You need to define 2 separate methods:

public static string MyMethod<T>(this IEnumerable<T> source) where T : struct
public static string MyMethod(this IEnumerable<string> source)

Solution 4

I used a hack-solution: interface. See the interfaces the built-in value types and string type have implemented:

struct Int32 : IComparable, IFormattable, IConvertible, IComparable<int>, IEquatable<int>

class String : IComparable, ICloneable, IConvertible, IComparable<string>, IEnumerable<char>, IEnumerable, IEquatable<string>

struct Boolean : IComparable, IConvertible, IComparable<bool>, IEquatable<bool>

struct DateTime : IComparable, IFormattable, IConvertible, ISerializable, IComparable<DateTime>, IEquatable<DateTime>

struct UInt64 : IComparable, IFormattable, IConvertible, IComparable<ulong>, IEquatable<ulong>

struct Single : IComparable, IFormattable, IConvertible, IComparable<float>, IEquatable<float>

struct Byte : IComparable, IFormattable, IConvertible, IComparable<byte>, IEquatable<byte>

struct Char : IComparable, IConvertible, IComparable<char>, IEquatable<char>

struct Decimal : IFormattable, IComparable, IConvertible, IComparable<decimal>, IEquatable<decimal>

You can use IComparable,IConvertible,IEquatable<T>for constraints. Like this:

 public static void SetValue<T>(T value) where T : IComparable, IConvertible, IEquatable<T>
    {
        //TODO:
    }

Or you can use type code to check the data time without constraints.

public static void SetValue<T>(T value)
    {
        switch (Type.GetTypeCode(typeof(T)))
        {
            #region These types are not what u want, comment them to throw ArgumentOutOfRangeException

            case TypeCode.Empty:
                break;
            case TypeCode.Object:
                break;
            case TypeCode.DBNull:

                #endregion

                break;
            case TypeCode.Boolean:
                break;
            case TypeCode.Char:
                break;
            case TypeCode.SByte:
                break;
            case TypeCode.Byte:
                break;
            case TypeCode.Int16:
                break;
            case TypeCode.UInt16:
                break;
            case TypeCode.Int32:
                break;
            case TypeCode.UInt32:
                break;
            case TypeCode.Int64:
                break;
            case TypeCode.UInt64:
                break;
            case TypeCode.Single:
                break;
            case TypeCode.Double:
                break;
            case TypeCode.Decimal:
                break;
            case TypeCode.DateTime:
                break;
            case TypeCode.String:
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
    }

Remember that do not use object type but generic type for parameter type. Otherwise you might get an NULL EXCEPTION at codeline Type.GetTypeCode(value.GetType()) when value is null.

Solution 5

You can use a static constructor to check the type parameter when the class is used.

class Gen<T> {
    static Gen() {
        if (!typeof(T).IsValueType && typeof(T) != typeof(String))
        {
            throw new ArgumentException("T must be a value type or System.String.");
        }
    }
}
Share:
36,174
Brett Postin
Author by

Brett Postin

Updated on January 26, 2020

Comments

  • Brett Postin
    Brett Postin over 4 years

    I'm trying to write an extension method on IEnumerable that will only apply to value types and strings.

    public static string MyMethod<T>(this IEnumerable<T> source) where T : struct, string
    

    However 'string' is not a valid constraint as it is a sealed class.

    Is there any way to do this?

    Edit:

    What I'm actually trying to do is prepare a list of values for an "IN" clause in a dynamically constructed SQL.

    I have lots of instances of code such as the following that I want to clean up:

    sb.AppendLine(string.Format("AND value IN ({0})", string.Join(",", Values.Select(x => x.ToSQL()).ToArray())));
    

    Where ToSQL() has code to handle SqlInjection.