“泛型、扩展方法、委托和反射”的概念及理解

1、泛型  

泛型是一种特殊的类型,它把指定类型的工作推迟到客户端代码声明并实例化类或方法的时候进行。

例如,通过使用泛型类型参数 T,您可以编写其他客户端代码能够使用的单个类,而不致引入运行时强制转换或装箱操作的成本或风险,如下所示:

复制代码
// 声明 generic 类
public class GenericList<T>
{
    void Add(T input) { }
}
class TestGenericList
{
    private class ExampleClass { }
    static void Main()
    {
        // 声明list为整数类型int
        GenericList<int> list1 = new GenericList<int>();

        // 声明list为字符类型string
        GenericList<string> list2 = new GenericList<string>();

        // 声明list为一个类
        GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
    }
}
复制代码

1、使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。
2、泛型最常见的用途是创建集合类。
3、.NET Framework 类库在 System.Collections.Generic 命名空间中包含几个新的泛型集合类。应尽可能地使用这些类来代替普通的类,如 System.Collections 命名空间中的 ArrayList。
4、您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
5、可以对泛型类进行约束以访问特定数据类型的方法。
6、关于泛型数据类型中使用的类型的信息可在运行时通过反射获取

.NET Framework类库在Collections.Generic命名空间中包含几个泛型集合类。List<T>类是ArrayList类的泛型等效类。使用大小可按需动态增加的数组实现IList泛型接口。动态数组的好处是不必事先设置较大的数组,减少不必要的内存开销。
微软MSDN的C#编程指南建议使用泛型集合类,如List<T>类,而不要使用非泛型集合类,如ArrayList类,也不要自行创建集合类。原因是不必做.NET Framework已经完成的工作,公共语言运行库能够共享Microsoft中间语言代码和元素据,这是自己编写的强类型所无法做到的。
下面举例说明List<T>的用法。

复制代码
public class Student  //学生类作为数据源
{
    public string Name { get; set; }
    public List<int> Scores { get; set; }  //成绩集合
}
static void Main(string[] args)
{
    //初始化泛型类List<Student>,集合中的元素包含一个成绩的内部序列List<int>
    List<Student> students = new List<Student>();
    students.Add(new Student { Name = "张三", Scores = new List<int> { 93, 72, 88, 78 } });
    students.Add(new Student { Name = "李四", Scores = new List<int> { 95, 71, 88, 68 } });
    //使用Linq查询
    var Query = from student in students
                where student.Scores.Average() >= 80
                select new
                { 
                    姓名 = student.Name, 
                    成绩 = student.Scores.Average()
                };
    foreach (var q in Query)
        Console.WriteLine("{0}  {1}", q.姓名, q.成绩);
}
输出:张三  82.75 / 李四 80.5
复制代码

 2、扩展方法

扩展方法使您能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。

扩展方法让大家很容易的向现有类型中添加方法(不破坏源类的内容)

写法,看代码:

复制代码
class Program 
{ 
    static void Main(string[] args) 
    { 
        Human human = new Human(); 
        human.Name = "张三"; 
        human.Age = 18; 
  
        human.GetName(); 
        human.GetAge("岁"); 
    } 
} 
  
public class Human 
{ 
    public string Name { get; set; } 
    public int Age { get; set; } 
} 
  
public static class HumanExtension 
{ 
    /// <summary> 
    /// 扩展Human类的方法GetName 
    /// </summary> 
    /// <param name="human"></param> 
    public static void GetName(this Human human) 
    { 
        Console.WriteLine("姓名:" + human.Name); 
    } 
    /// <summary> 
    /// 扩展Human类的方法GetAge 
    /// </summary> 
    /// <param name="human"></param> 
    /// <param name="unit">单位</param> 
    public static void GetAge(this Human human, string unit) 
    { 
        Console.WriteLine("年龄:" + human.Age + unit); 
    } 
}
复制代码

很容易吧,需要注意的是,扩展方法必须是在非泛型静态类中定义,并且扩展方法必须是静态的,方法的第一个参数必须是this [类型]。使用扩展方法,可以很容易的为我们已有的类添加方法,如给String类添加个ToSource方法等。

原类调用这个扩展的方法时,要通过类的实例调用(尽管这个扩展方法是静态的)

复制代码
Person.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Ccharp_001
{
public class Person
    {
      public string name { get; set; }
      public string phone { get; set; }
    }
}
//扩展方法:
PersonExtension.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Ccharp_001
{
public static class PersonExtension
    {
     public static string showInfo(this Person person)
     {
         return "姓名:"+person.name +"\t电话:"+ person.phone;
     }
    }
}
//主函数的调用:
class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person { name="小王",phone="15800000000" };
            Console.WriteLine(p.name );
            Console.WriteLine("下面是扩展类的方法调用");
            Console.WriteLine(p.showInfo ());
            while (true)
            { }
        }
    }
复制代码

 注:如果扩展方法名与类中已有的方法名相同,则优先调用类中原来的方法。

 3、委托

将方法作为方法的参数

先看一个例子:在屏幕上输出一句问候语:

public  void  GreetPeople( string  name)
{
   //....
   EnglishGreeting(name);
}
 
public  void  EnglishGreeting( string  name)
{
   Console.WriteLine( "Morning," +name);
}

暂且不管这两个方法有没有什么实际意义。GreetPeople用于向某人问好,当我们传递代表某人姓名的name参数,比如说“Jimmy”,进去的时候,在这个方法中,将调用EnglishGreeting方法,再次传递name参数,EnglishGreeting则用于向屏幕输出 “Morning, Jimmy”。

现在假设这个程序需要进行全球化,哎呀,不好了,我是中国人,我不明白“Morning”是什么意思,怎么办呢?好吧,我们再加个中文版的问候方法:

public void ChineseGreeting(string name)
{
Console.WriteLine("早上好, " + name);
}

 这时候,GreetPeople也需要改一改了,不然如何判断到底用哪个版本的Greeting问候方法合适呢?在进行这个之前,我们最好再定义一个枚举作为判断的依据:

复制代码
public enum Language
{
   English, Chinese
}
public void GreetPeople(string name, Language lang)
{
//做某些额外的事情,比如初始化之类,此处略
 switch(lang)
 {
  case Lang.English:
   EnglishGreeting(name);
   break;
  case Lang.Chinese:
   ChineseGreeting(name);
   break;
 }
}
复制代码

    OK,尽管这样解决了问题,但我不说大家也很容易想到,这个解决方案的可扩展性很差,如果日后我们需要再添加韩文版、日文版,就不得不反复修改枚举和GreetPeople()方法,以适应新的需求。
在考虑新的解决方案之前,我们先看看 GreetPeople的方法签名:
 public void GreetPeople(string name, Language lang)
我们仅看 string name,在这里,string 是参数类型,name 是参数变量,当我们赋给name字符串“jimmy”时,它就代表“jimmy”这个值;当我们赋给它“张子阳”时,它又代表着“张子阳”这个值。然后,我们可以在方法体内对这个name进行其他操作。哎,这简直是废话么,刚学程序就知道了。
    如果你再仔细想想,假如GreetPeople()方法可以接受一个参数变量,这个变量可以代表另一个方法,当我们给这个变量赋值 EnglishGreeting的时候,它代表着 EnglishGreeting() 这个方法;当我们给它赋值ChineseGreeting 的时候,它又代表着ChineseGreeting()方法。我们将这个参数变量命名为 MakeGreeting,那么不是可以如同给name赋值时一样,在调用 GreetPeople()方法的时候,给这个MakeGreeting 参数也赋上值么(ChineseGreeting或者EnglishGreeting等)?然后,我们在方法体内,也可以像使用别的参数一样使用MakeGreeting。但是,由于MakeGreeting代表着一个方法,它的使用方式应该和它被赋的方法(比如ChineseGreeting)是一样的,比如:
MakeGreeting(name);
好了,有了思路了,我们现在就来改改GreetPeople()方法,那么它应该是这个样子了:

public void GreetPeople(string name, *** MakeGreeting)
{
  MakeGreeting(name);
}
    注意到 *** ,这个位置通常放置的应该是参数的类型,但到目前为止,我们仅仅是想到应该有个可以代表方法的参数,并按这个思路去改写GreetPeople方法,现在就出现了一个大问题: 这个代表着方法的MakeGreeting参数应该是什么类型的?
NOTE:这里已不再需要枚举了,因为在给MakeGreeting赋值的时候动态地决定使用哪个方法,是ChineseGreeting还是 EnglishGreeting,而在这个两个方法内部,已经对使用“morning”还是“早上好”作了区分。
    聪明的你应该已经想到了,现在是委托该出场的时候了,但讲述委托之前,我们再看看MakeGreeting参数所能代表的 ChineseGreeting()和EnglishGreeting()方法的签名:
public void EnglishGreeting(string name)
public void ChineseGreeting(string name)
    如同name可以接受String类型的“true”和“1”,但不能接受bool类型的true和int类型的1一样。 MakeGreeting的 参数类型定义 应该能够确定 MakeGreeting可以代表的方法种类,再进一步讲,就是MakeGreeting可以代表的方法 的 参数类型和返回类型。
于是,委托出现了: 它定义了MakeGreeting参数所能代表的方法的种类,也就是MakeGreeting参数的类型。
NOTE:如果上面这句话比较绕口,我把它翻译成这样:string 定义了name参数所能代表的 值的种类,也就是name参数的类型。
本例中委托的定义:
public delegate void GreetingDelegate(string name);
可以与上面EnglishGreeting()方法的签名对比一下,除了加入了delegate关键字以外,其余的是不是完全一样?
现在,让我们再次改动GreetPeople()方法,如下所示:
public void GreetPeople(string name, GreetingDelegate MakeGreeting)
{
  MakeGreeting(name);
}
    如你所见,委托GreetingDelegate出现的位置与 string相同,string是一个类型,那么GreetingDelegate应该也是一个类型,或者叫类(Class)。但是委托的声明方式和类却完全不同,这是怎么一回事?实际上,委托在编译的时候确实会编译成类。因为Delegate是一个类,所以在任何可以声明类的地方都可以声明委托。更多的内容将在下面讲述,现在,请看看这个范例的完整代码:
复制代码
using System;
using System.Collections.Generic;
using System.Text;
namespace Delegate 
{
//定义委托,它定义了可以代表的方法的类型
public delegate void GreetingDelegate(string name);
class Program
{
 private static void EnglishGreeting(string name) 
 {
  Console.WriteLine("Morning, " + name);
 }
 private static void ChineseGreeting(string name) 
 {
  Console.WriteLine("早上好, " + name);
 }
//注意此方法,它接受一个GreetingDelegate类型的方法作为参数
 private static void GreetPeople(string name, GreetingDelegate MakeGreeting)
 {
  MakeGreeting(name);
 }
 static void Main(string[] args)
 {
  GreetPeople("Jimmy Zhang", EnglishGreeting);
  GreetPeople("张子阳", ChineseGreeting);
  Console.ReadKey();
 }
}
}
输出如下:
Morning, Jimmy Zhang
早上好, 张子阳
复制代码

 我们现在对委托做一个总结:

委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。

猜你喜欢

转载自www.cnblogs.com/Freedom0221/p/12378351.html