泛型技巧系列:如何提供类型参数之间的转换

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了泛型技巧系列:如何提供类型参数之间的转换相关的知识,希望对你有一定的参考价值。

这些心得大都是我在设计VBF中思考发现的,还受了很多C++模板与JAVA泛型的启发。其中相当多的技巧和概念都是大部分“深入C# 2.0”类文章也见不到的。我考虑把其中概念性的部分系统地整理成书,而技巧性的东西则通过我这个系列不定期地分享到Blog上。希望用到.NET泛型的人能从我这些技巧中受益。首先我要介绍的技巧是如何提供类型参数之间的转换。我们知道,.NET泛型将每个类型参数理解为一个独立的类型。如果不通过约束指定,编译器不会对类型参数所代表的类型做任何假设。也就是说,如果在某个上下文中有两个不同的类型参数U和V,编译器不会知道运行时他们代表的真实类型能否进行类型转换,因此会拒绝编译如下代码:Public Function GenericCast(Of U, V)(ByVal obj As U) As VReturnCType(obj, V)End Function除非我们加以约束U是V的子类。这显然是为了类型安全做出的考虑,因为.NET这种跨程序集的泛型无法在编译时做出足够的检查来确保类型安全。但是限制了这种操作,就妨碍了我们做事,很多代码因此写不出来。于是,我们可以利用.NET所有类型的基类Object来绕过这一限制:Return DirectCast(DirectCast(obj, Object), V)VB的DirectCast运算符在泛型的类型参数上作用与C#的括号运算符相同。也就是说,这段代码用C#写起来是这样:return (V)(object)obj;这样,即使不约束U和V之间的关系,这段代码也能编译了。但是他的功能却不能令我们满意。这样写出的类型转换实际上还是仅当U是V本身或其子类的时候才能转换成功。而其他一切情况都会转换失败。不管U和V的运行时类型之间是否定义有其他类型转换规则。这显然不合我们意愿,我们希望int与double之间的转换等语言内置的类型转换都能够自动进行,否则就和约束没什么两样了。对VB用户来说有一个极为简单的解决方案——把第二次DirectCast变成CType:Public Function GenericCast(Of U, V)(ByVal obj As U) As VReturnCType(DirectCast(obj, Object), V)End Function现在,GenericCast泛型方法就能执行int与double等内置规则的转换了。很神奇?因为CType运算符在编译时自动调用了VB运行库的转换函数,该函数在运行期间对泛型类型参数的真实类型做了检查。而且这个过程的性能完全可以接受。现在VB的用户已经轻松享受这一功能了。但是C#的事情还没完,因为C#没有如此智能的类型转换运算符,因此就需要手工实现VB运行库所代办的那些任务。其实就是利用了一下IConvertible:static V GenericCast<U, V>(U obj)IConvertible convertibleObj = obj as IConvertible;if (convertibleObj != null)Type t = typeof(V); switch (Type.GetTypeCode(t))case TypeCode.Boolean:return (V)(object)convertibleObj.ToBoolean(null);case TypeCode.Byte:return (V)(object)convertibleObj.ToByte(null);case TypeCode.Char:return (V)(object)convertibleObj.ToChar(null);break; return (V)(object)obj;不过这个方法看起来不但很丑、很麻烦,功能上还达不到VB版。所以建议您用到这种类型参数之间的转换,就用VB封装这一功能,然后做成dll供C#调用吧。 参考技术A 泛型可以用"<T>"代表,任意类型的,只要在泛型中定义了类型,之后T类型就会变为相应的类型的。举例:
List<String> list = new ArayList<String>();这个就定义了一个String类型的”泛型“集合,那么T的类型就是字符串。
List<T> list = new ArayList<T>();
可以赋值给list:list.add("StringBatch");
可以获取到list的值:list.get(0),结果就是”StringBatch“;
这个时候T的类型也是String。也就是说T是动态的,可以被任意指定类型。

C# 泛型的使用

01

泛型概述

       泛型是用于处理算法、数据结构的一种编程方法。泛型的目标是采用广泛适用和可交互性的形式来表示算法和数据结构,以使它们能够直接用于软件构造。泛型类、结构、接口、委托和方法可以根据它们存储和操作的数据的类型来进行参数化。泛型能在编译时提供强大的类型检查,减少数据类型之间的显式转换、装箱操作和运行时的类型检查。泛型类和泛型方法同时具备可重用性、类型安全和效率高等特性,这是非泛型类和非泛型方法无法具备的。泛型通常用在集合和在集合上运行的方法中。

       泛型主要是提高了代码的重用性,例如,可以将泛型看成是一个可以回收的包装箱 A。如果在包装箱 A 上贴上苹果标签,就可以在包装箱A 里装上苹果进行发送;如果在包装箱 A 上贴上地瓜标签就可以在包装箱 A 里装上地瓜进行发送。

02


泛型的使用

       在下面内容中将会详细介绍泛型的类型参数 T,以及如何创建泛型接口和泛型方法,并且通过实例演示泛型接口和泛型方法在程序中的应用。

1. 类型参数T

泛型的类型参数 T 可以看作是一个占位符,它不是一种类型,它仅代表了某种可能的类型。类型参数T可以在使用时用任何类型来代替。类型参数T的命名准则如下。

        使用描述性名称命名泛型类型参数,除非单个字母名称完全可以让人了解它表示的含义,而描述性名称不会有更多的意义。

     【例1】 使用代表一定意义的单词作为类型参数T的名称。

      代码如下:

   public interface ISessionChannel<Session>

   public delegate TOutput Converter<input, Output>

       将T作为描述性类型参数名的前缀。

    【例2】 使用T作为类型参数名的前缀。

      代码如下:

public interface ISessionChannel<TSession>
  {     
     TSession Session{ get;}
  }

2.  泛型接口

泛型接口的声明形式如下: 

interface 【接口名】<T>

{

          【接口体】

}

       声明泛型接口时,与声明一般接口的唯一区别是增加了一个<T>。一般来说,声明泛型接口与声明非泛型接口遵循相同的规则。泛型类型声明所实现的接口必须对所有可能的构造类型都保持唯一,否则就无法确定该为某些构造类型调用哪个方法。

       说明 

              在实例化泛型时也可以使用约束对类型参数的类型种类施加限制,约束是使用 where 上下文关键字指定的,下面列出了 6种类型的约束。

      ① T:结构——类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。

      ② T:类——类型参数必须是引用类型。这一点也适用于任何类、接口、委托或数组类型。

      ③ T:new()——类型参数必须具有无参数的公共构造函数,当与其他约束一起使用时,new()约束必须最后指定。

      ④ T:<基类名>——类型参数必须是指定的基类或派生自指定的基类。

      ⑤ T:<接口名称>——类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束,约束接口也可以是泛型的。

      ⑥ T:U——为T提供的类型参数必须是为 U提供的参数或派生自为 U提供的参数,这称为裸类型约束。

     【例3】 创建一个控制台应用程序,首先创建一个 Factory 类,在该类中建立一个CreateInstance方法。然后再创建一个泛型接口,在这个泛型接口中调用 Createlnstance 方法。根据类型参数 T,获取其类型。

      代码如下:

//创建一个泛型接口
  public interface IGenericInterface<T>
  {     
       T CreateInstance();    //接口中调用 CreateInstance 方法 
  }
  //实现上面泛型接口的泛型类
  //派生约束 where T: TI(T 要继承自 TI)
  //构造函数约束 where T:new()(T 可以实例化)
  public class Factory<T,TI>;IGenericInterface<TI> where T: TI,new()
  {     
  public TI CreateInstance()    //创建一个公共方法 CreateInstance
  {          
       return new T();    
  }
}
class Program
{    
    static void Main(string[] args)    
    {
        //实例化接口         
        IGenericInterface<System.ComponentModel.IListSource> factory =
        new Factory<System.Data.DataTable,System.ComponentModel.IListSource>();   
        //输出指定泛型的类型         
        Console.WriteLine(factory.CreateInstance().GetType().ToString());  
        Console.ReadLine();    
    }
}

程序的运行结果如图1 所示。

图1  泛型接口应用

3.  泛型方法

泛型方法的声明形式如下:

【修饰符】 Void 【方法名】<类型参数T>

{

    【方法体】

}

泛型方法是在声明中包括了类型参数T的方法。泛型方法可以在类、结构或接口声明中声明,这些类、结构或接口本身可以是泛型或非泛型。如果在泛型类型声明中声明泛型方法,则方法体可以同时引用该方法的类型参数T和包含该方法的声明的类型参数 T。

说明

       泛型方法可以使用多类型参数进行重载。

     【例4】 创建一个控制台应用程序,通过定义一个泛型方法来查找数组中某个数字的位置。

      代码如下:

public class Finder     //建立一个公共类 Finder
  {
  public static int Find<T>(T[] items,T item)     //创建泛型方法 
  {
      for(int i= 0;i < items.Length;i++)          //调用 for 循环 
      {
           if(items[i].Equals(item))            //调用 Equals 方法比较两个数 
           {
                 return i;                     //返回相等数在数组中的位置
           }
      }
      return -1;                              //如果不存在指定的数,则返回-1 
   }
}
class Program
{
    static void Main(string[] args)
    {
         int i= Finder.Find<int>(new int[](1,2,3,4,5,6,8,9),6);    //调用泛型方法,并定义数组指定数字 
         Console.WriteLine("6 在数组中的位置:"+i.ToString());     //输出中数字在数组中的位置 
         Console.ReadLine();
     }
}

程序的运行结果是“6 在数组中的位置为5”。

以上是关于泛型技巧系列:如何提供类型参数之间的转换的主要内容,如果未能解决你的问题,请参考以下文章

C#进阶系列11 泛型

如何将枚举泛型类型的变量转换为 int 并返回

当两个类型参数在C#中实现公共接口时,如何将泛型强制转换为它实现的接口

Golang 泛型实现类型转换

Golang 泛型实现类型转换

java 反射技巧