C # 泛型和类型检查

我有一个使用 IList<T>作为参数的方法。我需要检查这个 T对象的类型,然后根据它做一些事情。我试图使用 T值,但编译器不允许。我的解决办法如下:

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

肯定有更好的办法。有没有什么方法可以检查传入的 T类型,然后使用 switch语句?

148483 次浏览

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

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

You can use typeof(T).

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

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.

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.

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.

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

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.

How about this :

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

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

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

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