C# 泛型和类型检查

Posted

技术标签:

【中文标题】C# 泛型和类型检查【英文标题】:C# Generics and Type Checking 【发布时间】:2010-11-02 05:57:43 【问题描述】:

我有一个使用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】:

你可以使用重载:

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

【讨论】:

+1:就设计和长期可维护性而言,重载绝对是这里的最佳解决方案。对泛型参数进行运行时类型检查似乎太讽刺了,无法直接编写代码。 一个很好的例子,当这将是有用的时,泛型序列化具有广泛变化的类型。如果传入的对象是字符串,为什么要额外工作?如果是字符串,直接返回原字符串,不做任何额外处理 抱歉,有没有办法通过使用switch-case 而不是if-else 来实现? @HappyCoding 很遗憾没有 =( 你可以在下一版本的 C# 中这样做。 如果你的重载在功能上是不同的(也要考虑副作用),你不应该依赖泛型重载(参见this answer),因为你不能保证会调用更专业的重载。这里的规则是:如果您必须为特定类型执行专门的逻辑,那么您必须检查该类型并且不要使用重载;但是,如果您只喜欢执行专门的逻辑(即为了提高性能),但包括一般情况在内的所有重载都会导致相同的结果,那么您可以使用重载而不是类型检查。【参考方案2】:

您可以使用typeof(T)

private static string BuildClause<T>(IList<T> clause)

     Type itemType = typeof(T);
     if(itemType == typeof(int) || itemType == typeof(decimal))
    ...

【讨论】:

【参考方案3】:

默认情况下知道没有什么好方法。不久前,我对此感到沮丧,并编写了一个小实用程序类,它提供了一些帮助并使语法更清晰。本质上它把代码变成了

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

完整的博客文章和实施细节可在此处获得

http://blogs.msdn.com/jaredpar/archive/2008/05/16/switching-on-types.aspx

【讨论】:

【参考方案4】:

而且,由于 C# 已经发展,您(现在)可以使用 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");
        
    

在 C# 8.0 中再次使用 switch expressions,语法变得更加简洁。

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

【讨论】:

【参考方案5】:

希望对您有所帮助:

typeof(IList&lt;T&gt;).IsGenericType == true typeof(IList&lt;T&gt;).GetGenericTypeDefinition() == typeof(IList&lt;&gt;) typeof(IList&lt;int&gt;).GetGenericArguments()[0] == typeof(int)

https://dotnetfiddle.net/5qUZnt

【讨论】:

【参考方案6】:

typeof 运算符...

typeof(T)

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

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

...这会让你写出这样的代码:

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 的答案。【参考方案7】:

没有办法使用 switch 语句来完成你想要它做的事情。 switch 语句必须提供整数类型,不包括复杂类型,例如“类型”对象或任何其他对象类型。

【讨论】:

【参考方案8】:

对于所有说检查类型并基于类型做某事对于泛型来说不是一个好主意的人我有点同意,但我认为在某些情况下这完全有道理。

例如,如果你有一个类说是这样实现的(注意:为了简单起见,我没有展示这段代码所做的一切,只是简单地剪切并粘贴到这里,因此它可能无法像整个代码可以,但它明白了这一点。此外,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();

因此,总而言之,我认为您可能需要检查泛型是什么类型,以便做一些特别的事情,这是有充分理由的。

【讨论】:

【参考方案9】:

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

【讨论】:

【参考方案10】:

您可以使用typeOf(T),但我会仔细检查您的方法,并确保您在这里没有违反单一责任。这将是一种代码气味,这并不是说不应该这样做,而是您应该小心。

如果您不关心类型是什么,或者只要它符合特定的一组标准,泛型的重点就在于能够构建与类型无关的算法。您的实现不是很通用。

【讨论】:

【参考方案11】:

这个怎么样:

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

【讨论】:

以上是关于C# 泛型和类型检查的主要内容,如果未能解决你的问题,请参考以下文章

swift中泛型和 Any 类型

C#中的泛型和泛型集合

简单介绍-泛型和包装类数据结构

C# 泛型和方法

使用泛型和合并对象的 Typescript 声明文件

C# 泛型的使用