Jon
Jon

Reputation: 2279

C# Generics and Type Checking

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?

Upvotes: 130

Views: 189795

Answers (12)

eduherminio
eduherminio

Reputation: 1674

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

Upvotes: 2

Kit
Kit

Reputation: 21699

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

Upvotes: 26

Jaider
Jaider

Reputation: 14874

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

Upvotes: 12

JoshBerke
JoshBerke

Reputation: 67068

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.

Upvotes: 2

bdowden
bdowden

Reputation: 1021

You can use typeof(T).

private static string BuildClause<T>(IList<T> clause)
{
     Type itemType = typeof(T);
     if(itemType == typeof(int) || itemType == typeof(decimal))
    ...
}

Upvotes: 32

Bert
Bert

Reputation: 1

How about this :

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

Upvotes: 0

John
John

Reputation: 56

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.

Upvotes: 3

JaredPar
JaredPar

Reputation: 754525

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

Upvotes: 8

Robert Harvey
Robert Harvey

Reputation: 180787

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

Upvotes: 4

mqp
mqp

Reputation: 71937

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.

Upvotes: 2

womp
womp

Reputation: 116977

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.

Upvotes: 3

jonnii
jonnii

Reputation: 28312

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

Upvotes: 174

Related Questions