115

I have a method that uses an IList<T> as a parameter. I need to check what the type of that T object is and do something based on it. I was trying to use the T value, but the compiler does not not allow it. My solution is the following:

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        if (clause[0] is int || clause[0] is decimal)
        {
           //do something
        }
        else if (clause[0] is String)
        {
           //do something else
        }
        else if (...) //etc for all the types
        else
        {
           throw new ApplicationException("Invalid type");
        }
    } 
}

There has to be a better way to do this. Is there some way I can check the type of T that is passed in and then use a switch statement?

J0e3gan
  • 8,740
  • 10
  • 53
  • 80
Jon
  • 2,129
  • 7
  • 23
  • 31
  • 1
    I'd personally like to know what you're doing special for each data type. If you're doing roughly the same transformation for each data type, it might be easier to map different types to a common interface and operate on that interface. – Juliet Jun 11 '09 at 19:18

12 Answers12

156

You could use overloads:

public static string BuildClause(List<string> l){...}

public static string BuildClause(List<int> l){...}

public static string BuildClause<T>(List<T> l){...}

Or you could inspect the type of the generic parameter:

Type listType = typeof(T);
if(listType == typeof(int)){...}
jonnii
  • 28,019
  • 8
  • 80
  • 108
  • 30
    +1: overloads are definitely the *best* solution here in terms of design and long-term maintainability. A runtime type-check of a generic parameter just seems too ironic to code with a straight face. – Juliet Jun 11 '09 at 19:14
  • A great example of when this would be useful is generic serialization with wildly varying types. If the object being passed in is a string, why do the extra work? If it's a string, just return the original string without attempting any extra processing – TodoCleverNameHere Mar 23 '15 at 19:14
  • Sorry, is there a way to achieve that via using `switch-case` instead of `if-else`? – Tân Sep 30 '16 at 05:27
  • @HappyCoding unfortunately not =( you may be able to do so with the next version of C#. – jonnii Oct 04 '16 at 00:53
  • 9
    You should NOT rely on generic overloading (see [this answer](http://stackoverflow.com/a/601301/1631910)) if your overloads are functionally different (consider side-effects too), because you can't guarantee a more specialized overload will be called. Rule here is this: if you HAVE to do specialized logic for a specific type then you must check for that type and not use overloading; however, if you only PREFER to do specialized logic (i.e. for performance improvements) but all overloads including the general case result in the same outcome then you can use overloading instead of type checking. – tomosius Mar 14 '17 at 06:52
30

You can use typeof(T).

private static string BuildClause<T>(IList<T> clause)
{
     Type itemType = typeof(T);
     if(itemType == typeof(int) || itemType == typeof(decimal))
    ...
}
J0e3gan
  • 8,740
  • 10
  • 53
  • 80
bdowden
  • 993
  • 7
  • 11
19

And, because C# has evolved, you can (now) use pattern matching.

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        switch (clause[0])
        {
            case int x: // do something with x, which is an int here...
            case decimal x: // do something with x, which is a decimal here...
            case string x: // do something with x, which is a string here...
            ...
            default: throw new Exception("Invalid type");
        }
    }
}

And again with switch expressions in C# 8.0, the syntax gets even more succinct.

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        return clause[0] switch
        {
            int x => "some string related to this int",
            decimal x => "some string related to this decimal",
            string x => x,
            ...,
            _ => throw new Exception("Invalid type")
        }
    }
}
Kit
  • 20,354
  • 4
  • 60
  • 103
  • I really like that improvement. Especially for result files with varying input (sometimes int, sometimes double, sometimes string), it always felt kind of stupid to define several overloads. – Thern May 08 '23 at 01:10
11

I hope you find this helpful:

  • typeof(IList<T>).IsGenericType == true
  • typeof(IList<T>).GetGenericTypeDefinition() == typeof(IList<>)
  • typeof(IList<int>).GetGenericArguments()[0] == typeof(int)

https://dotnetfiddle.net/5qUZnt

Jaider
  • 14,268
  • 5
  • 75
  • 82
8

By default know there is not a great way. Awhile back I got frustrated with this and wrote a little utility class that helped out a bit and made the syntax a bit cleaner. Essentially it turns the code into

TypeSwitcher.Do(clause[0],
  TypeSwitch.Case<int>(x => ...),  // x is an int
  TypeSwitch.Case<decimal>(d => ...), // d is a decimal 
  TypeSwitch.Case<string>(s => ...)); // s is a string

Full blog post and details on the implementation are available here

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
4

The typeof operator...

typeof(T)

... won't work with the c# switch statement. But how about this? The following post contains a static class...

Is there a better alternative than this to 'switch on type'?

...that will let you write code like this:

TypeSwitch.Do(
    sender,
    TypeSwitch.Case<Button>(() => textBox1.Text = "Hit a Button"),
    TypeSwitch.Case<CheckBox>(x => textBox1.Text = "Checkbox is " + x.Checked),
    TypeSwitch.Default(() => textBox1.Text = "Not sure what is hovered over"));
Community
  • 1
  • 1
Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
3

For everyone that says checking types and doing something based on the type is not a great idea for generics I sort of agree but I think there could be some circumstances where this perfectly makes sense.

For example if you have a class that say is implemented like so (Note: I am not showing everything that this code does for simplicity and have simply cut and pasted into here so it may not build or work as intended like the entire code does but it gets the point across. Also, Unit is an enum):

public class FoodCount<TValue> : BaseFoodCount
{
    public TValue Value { get; set; }

    public override string ToString()
    {
        if (Value is decimal)
        {
            // Code not cleaned up yet
            // Some code and values defined in base class

            mstrValue = Value.ToString();
            decimal mdecValue;
            decimal.TryParse(mstrValue, out mdecValue);

            mstrValue = decimal.Round(mdecValue).ToString();

            mstrValue = mstrValue + mstrUnitOfMeasurement;
            return mstrValue;
        }
        else
        {
            // Simply return a string
            string str = Value.ToString() + mstrUnitOfMeasurement;
            return str;
        }
    }
}

...

public class SaturatedFat : FoodCountWithDailyValue<decimal>
{
    public SaturatedFat()
    {
        mUnit = Unit.g;
    }

}

public class Fiber : FoodCount<int>
{
    public Fiber()
    {
        mUnit = Unit.g;
    }
}

public void DoSomething()
{
       nutritionFields.SaturatedFat oSatFat = new nutritionFields.SaturatedFat();

       string mstrValueToDisplayPreFormatted= oSatFat.ToString();
}

So in summary, I think there are valid reasons why you might want to check to see what type the generic is, in order to do something special.

Dio F
  • 2,458
  • 1
  • 22
  • 39
John
  • 56
  • 2
3

There is no way to use the switch statement for what you want it to do. The switch statement must be supplied with integral types, which does not include complex types such as a "Type" object, or any other object type for that matter.

womp
  • 115,835
  • 26
  • 236
  • 269
2

You can do typeOf(T), but I would double check your method and make sure your not violating single responsability here. This would be a code smell, and that's not to say it shouldn't be done but that you should be cautious.

The point of generics is being able to build type-agnostic algorthims were you don't care what the type is or as long as it fits within a certain set of criteria. Your implementation isn't very generic.

J0e3gan
  • 8,740
  • 10
  • 53
  • 80
JoshBerke
  • 66,142
  • 25
  • 126
  • 164
2

Your construction completely defeats the purpose of a generic method. It's ugly on purpose because there must be a better way to achieve what you're trying to accomplish, although you haven't given us quite enough information to figure out what that is.

mqp
  • 70,359
  • 14
  • 95
  • 123
0

How about this :

            // Checks to see if the value passed is valid. 
            if (!TypeDescriptor.GetConverter(typeof(T)).IsValid(value))
            {
                throw new ArgumentException();
            }
Bert
  • 1
0

My two cents:

In case you happen to have a generic method that returns a generic value but doesn't have generic parameters, you can use default(T) + (T)(object) cast, together with C# 8 pattern matching/type checks (as indicated in the other recent answers).

Example:

private static T Parse<T>(string str)
{
    return default(T) switch
    {
        short => (T)(object)short.Parse(str),
        ushort => (T)(object)ushort.Parse(str),
        int => (T)(object)int.Parse(str),
        uint => (T)(object)uint.Parse(str),
        long => (T)(object)long.Parse(str),
        ulong => (T)(object)ulong.Parse(str),
        _ => throw new ArgumentException()
    };
}
eduherminio
  • 1,514
  • 1
  • 15
  • 31
  • Depends on T. If T is an object, `default(T)` will be null and most of the cases will be `_ => Exception` – Yauheni Pakala Mar 01 '23 at 10:29
  • I wouldn't use the word `object` there, but yeah, I get what you mean. This is just a trick that works for non-nullable types whose default value isn't null, like the ones provided in the example, in case it's ever useful to somebody. – eduherminio Mar 01 '23 at 11:00