C#泛型和类型检查


82

我有一个使用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语句?


1
我个人想知道您对每种数据类型所做的特殊操作。如果您对每种数据类型进行大致相同的转换,则将不同类型映射到公共接口并在该接口上进行操作可能会更容易。
朱丽叶

Answers:


120

您可以使用重载:

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

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

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

或者,您可以检查通用参数的类型:

Type listType = typeof(T);
if(listType == typeof(int)){...}

23
+1:就设计和长期可维护性而言,过载绝对是最好的解决方案。泛型参数的运行时类型检查似乎太具有讽刺意味,无法直接编写代码。
朱丽叶2009年

有用的一个很好的例子是类型繁多的通用序列化。如果传入的对象是字符串,为什么还要做额外的工作?如果是字符串,只需返回原始字符串即可,无需尝试任何额外的处理
watkinsmatthewp 2015年

抱歉,有没有一种方法可以通过使用switch-case代替来实现if-else
2016年

不幸的是,@HappyCoding不是=(您可能可以使用下一版本的C#
。– jonnii

7
如果您的重载在功能上有所不同(也要考虑副作用),则不应依赖于通用重载(请参见此答案),因为您不能保证会调用更专门的重载。这里的规则是这样的:如果您必须对特定类型进行专门的逻辑处理,则必须检查该类型并且不要使用重载;但是,如果您仅偏好执行专门的逻辑(即为了提高性能),但是所有重载(包括一般情况)都导致相同的结果,那么您可以使用重载代替类型检查。
tomosius

23

您可以使用typeof(T)

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

7

默认情况下,知道这不是一个好方法。不久前,我对此感到沮丧,并编写了一个小实用程序类,该类有所帮助,并使语法更简洁。本质上,它将代码转换为

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

完整的博客文章和有关实现的详细信息在这里


6

而且,由于C#已经发展,您可以(现在)使用模式匹配

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 ApplicationException("Invalid type");
        }
    }
}

再次使用C#8.0中的switch表达式,语法变得更加简洁。

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 ApplicationException("Invalid type")
        }
    }
}

4

typeof运算子...

typeof(T)

...不适用于c#switch语句。但是呢?以下帖子包含一个静态类...

有没有比“打开类型”更好的选择了?

...这样您就可以编写如下代码:

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

另请参见JaredPar的答案。
罗伯特·哈维

3

对于每个说过检查类型并基于类型进行操作的人来说,对于泛型而言,这不是一个好主意,但我认为在某些情况下这完全有意义。

例如,如果您有一个这样说实现的类(注意:我不是为了简单起见展示此代码所做的一切,而是只是剪切并粘贴到此处,因此它可能无法像整个代码那样按预期方式构建或工作,但是它可以说明要点。此外,Unit是一个枚举):

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

因此,总而言之,我认为出于正当理由,您可能需要检查泛型是什么类型,以便做一些特殊的事情。


2

无法将switch语句用于您要执行的操作。必须为switch语句提供整数类型,其中不包括复杂类型,例如“ Type”对象或与此相关的任何其他对象类型。


2

您的构造完全违反了通用方法的目的。这是故意的丑陋,因为必须有更好的方法来实现您要完成的目标,尽管您没有给我们足够的信息来弄清楚这是什么。


2

您可以这样做typeOf(T),但是我会仔细检查您的方法,并确保您在这里不违反单一职责。这将是一种代码味道,但这并不是说不应该这样做,而是要保持谨慎。

只要您不关心类型是什么,或者只要它符合一组特定条件,泛型就可以构建与类型无关的算法。您的实现不是很通用。


2

我希望这个对你有用:

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

https://dotnetfiddle.net/5qUZnt


0

这个怎么样 :

            // Checks to see if the value passed is valid. 
            if (!TypeDescriptor.GetConverter(typeof(T)).IsValid(value))
            {
                throw new ArgumentException();
            }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.