C# 泛型、委托

泛型 generic

一、什么是泛型

泛型是C#2.0推出的新语法,不是语法糖,而是2.0由框架升级提供的功能。

我们在编程程序时,经常会遇到功能非常相似的模块,只是它们处理的数据不一样。但我们没有办法,只能分别写多个方法来处理不同的数据类型。这个时候,那么问题来了,有没有一种办法,用同一个方法来处理传入不同种类型参数的办法呢?泛型的出现就是专门来解决这个问题的。

二、为什么使用泛型

先来看下面一个例子:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
    public class CommonMethod
    {
        /// <summary>
        /// 打印个int值
        /// 
        /// 因为方法声明的时候,写死了参数类型
        /// 已婚的男人 Eleven San
        /// </summary>
        /// <param name="iParameter"></param>
        public static void ShowInt(int iParameter)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
                typeof(CommonMethod).Name, iParameter.GetType().Name, iParameter);
        }

        /// <summary>
        /// 打印个string值
        /// </summary>
        /// <param name="sParameter"></param>
        public static void ShowString(string sParameter)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
                typeof(CommonMethod).Name, sParameter.GetType().Name, sParameter);
        }

        /// <summary>
        /// 打印个DateTime值
        /// </summary>
        /// <param name="oParameter"></param>
        public static void ShowDateTime(DateTime dtParameter)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
                typeof(CommonMethod).Name, dtParameter.GetType().Name, dtParameter);
        }
    }
}

结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9LJQctKi-1581865397029)(https://s2.ax1x.com/2019/03/09/ASl7od.md.png)]

从上面的结果中我们可以看出这三个方法,除了传入的参数不同外,其里面实现的功能都是一样的。在1.0版的时候,还没有泛型这个概念,那么怎么办呢。相信很多人会想到了OOP三大特性之一的继承,我们知道,C#语言中,object是所有类型的基类,将上面的代码进行以下优化:

public static void ShowObject(object oParameter)
{
      Console.WriteLine("This is {0},parameter={1},type={2}",
         typeof(CommonMethod), oParameter.GetType().Name, oParameter);
}

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-psO70lJk-1581865397030)(https://s2.ax1x.com/2019/03/09/ASlqJI.md.png)]

从上面的结果中我们可以看出,使用Object类型达到了我们的要求,解决了代码的可复用。可能有人会问定义的是object类型的,为什么可以传入int、string等类型呢?原因有二:

  1. object类型是一切类型的父类。

  2. 通过继承,子类拥有父类的一切属性和行为,任何父类出现的地方,都可以用子类来代替。

但是上面object类型的方法又会带来另外一个问题:装箱和拆箱,会损耗程序的性能。

微软在C#2.0的时候推出了泛型,可以很好的解决上面的问题。

三、泛型类型参数

在泛型类型或方法定义中,类型参数是在其实例化泛型类型的一个变量时,客户端指定的特定类型的占位符。 泛型类( GenericList)无法按原样使用,因为它不是真正的类型;它更像是类型的蓝图。 若要使用 GenericList,客户端代码必须通过指定尖括号内的类型参数来声明并实例化构造类型。 此特定类的类型参数可以是编译器可识别的任何类型。 可创建任意数量的构造类型实例,其中每个使用不同的类型参数。

上面例子中的代码可以修改如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
    public class GenericMethod
    {
        /// <summary>
        /// 泛型方法
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tParameter"></param>
        public static void Show<T>(T tParameter)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
                typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString());
        }
    }
}

调用:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
   class Program
   {
       static void Main(string[] args)
       {

           int iValue = 123;
           string sValue = "456";
           DateTime dtValue = DateTime.Now;

           Console.WriteLine("***********CommonMethod***************");
           CommonMethod.ShowInt(iValue);
           CommonMethod.ShowString(sValue);
           CommonMethod.ShowDateTime(dtValue);
           Console.WriteLine("***********Object***************");
           CommonMethod.ShowObject(iValue);
           CommonMethod.ShowObject(sValue);
           CommonMethod.ShowObject(dtValue);
           Console.WriteLine("***********Generic***************");
           GenericMethod.Show<int>(iValue);
           GenericMethod.Show<string>(sValue);
           GenericMethod.Show<DateTime>(dtValue);
           Console.ReadKey();
       }
   }
}

显示结果:
在这里插入图片描述

为什么泛型可以解决上面的问题呢?

泛型是延迟声明的:即定义的时候没有指定具体的参数类型,把参数类型的声明推迟到了调用的时候才指定参数类型。 延迟思想在程序架构设计的时候很受欢迎。例如:分布式缓存队列、EF的延迟加载等等。

泛型究竟是如何工作的呢?

控制台程序最终会编译成一个exe程序,exe被点击的时候,会经过JIT(即时编译器)的编译,最终生成二进制代码,才能被计算机执行。泛型加入到语法以后,VS自带的编译器又做了升级,升级之后编译时遇到泛型,会做特殊的处理:生成占位符。再次经过JIT编译的时候,会把上面编译生成的占位符替换成具体的数据类型。请看下面一个例子:

Console.WriteLine(typeof(List<>));
Console.WriteLine(typeof(Dictionary<,>));

结果:
在这里插入图片描述
从上面的截图中可以看出:泛型在编译之后会生成占位符。

注意:占位符需要在英文输入法状态下才能输入,只需要按一次波浪线(数字1左边的键位)的键位即可,不需要按Shift键。

泛型性能问题

请看一下的一个例子,比较普通方法、Object参数类型的方法、泛型方法的性能。

添加一个Monitor类,让三种方法执行同样的操作,比较用时长短:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
    public class Monitor
    {
        public static void Show()
        {
            Console.WriteLine("****************Monitor******************");
            {
                int iValue = 12345;
                long commonSecond = 0;
                long objectSecond = 0;
                long genericSecond = 0;

                {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    for (int i = 0; i < 100000000; i++)
                    {
                        ShowInt(iValue);
                    }
                    watch.Stop();
                    commonSecond = watch.ElapsedMilliseconds;
                }
                {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    for (int i = 0; i < 100000000; i++)
                    {
                        ShowObject(iValue);
                    }
                    watch.Stop();
                    objectSecond = watch.ElapsedMilliseconds;
                }
                {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    for (int i = 0; i < 100000000; i++)
                    {
                        Show<int>(iValue);
                    }
                    watch.Stop();
                    genericSecond = watch.ElapsedMilliseconds;
                }
                Console.WriteLine("commonSecond={0},objectSecond={1},genericSecond={2}"
                    , commonSecond, objectSecond, genericSecond);
            }
        }

        #region PrivateMethod
        private static void ShowInt(int iParameter)
        {
            //do nothing
        }
        private static void ShowObject(object oParameter)
        {
            //do nothing
        }
        private static void Show<T>(T tParameter)
        {
            //do nothing
        }
        #endregion

    }
}

Main()方法调用:

Monitor.Show();

结果:
在这里插入图片描述

从结果中可以看出:泛型方法的性能最高,其次是普通方法,object方法的性能最低。

四、泛型类

除了方法可以是泛型以外,类也可以是泛型的,例如:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
    /// <summary>
    /// 泛型类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class GenericClass<T>
    {
        public T _T;
    }
}

Main()方法中调用:

// T是int类型
GenericClass<int> genericInt = new GenericClass<int>();
genericInt._T = 123;
// T是string类型
GenericClass<string> genericString = new GenericClass<string>();
genericString._T = "123";

除了可以有泛型类,也可以有泛型接口,例如:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
    /// <summary>
    /// 泛型接口
    /// </summary>
    public interface IGenericInterface<T>
    {
        //泛型类型的返回值
        T GetT(T t);
    }
}

也可以有泛型委托:

public delegate void SayHi<T>(T t);//泛型委托

注意:

  1. 泛型在声明的时候可以不指定具体的类型,但是在使用的时候必须指定具体类型,例如:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
    /// <summary>
    /// 使用泛型的时候必须指定具体类型,
    /// 这里的具体类型是int
    /// </summary>
    public class CommonClass :GenericClass<int>
    {
    }
}

如果子类也是泛型的,那么继承的时候可以不指定具体类型,例如:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
    /// <summary>
    /// 使用泛型的时候必须指定具体类型,
    /// 这里的具体类型是int
    /// </summary>
    public class CommonClass :GenericClass<int>
    {
    }

    /// <summary>
    /// 子类也是泛型的,继承的时候可以不指定具体类型
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class CommonClassChild<T>:GenericClass<T>
    {

    }
}
  1. 类实现泛型接口也是这种情况,例如:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
    /// <summary>
    /// 必须指定具体类型
    /// </summary>
    public class Common : IGenericInterface<string>
    {
        public string GetT(string t)
        {
            throw new NotImplementedException();
        }
    }

    /// <summary>
    /// 可以不知道具体类型,但是子类也必须是泛型的
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class CommonChild<T> : IGenericInterface<T>
    {
        public T GetT(T t)
        {
            throw new NotImplementedException();
        }
    }
}

五、泛型约束

先来看看下面的一个例子:

定义一个People类,里面有属性和方法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
    public interface ISports
    {
        void Pingpang();
    }

    public interface IWork
    {
        void Work();
    }


    public class People
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public void Hi()
        {
            Console.WriteLine("Hi");
        }
    }

    public class Chinese : People, ISports, IWork
    {
        public void Tradition()
        {
            Console.WriteLine("仁义礼智信,温良恭俭让");
        }
        public void SayHi()
        {
            Console.WriteLine("吃了么?");
        }

        public void Pingpang()
        {
            Console.WriteLine("打乒乓球...");
        }

        public void Work()
        {
            throw new NotImplementedException();
        }
    }

    public class Hubei : Chinese
    {
        public Hubei(int version)
        { }

        public string Changjiang { get; set; }
        public void Majiang()
        {
            Console.WriteLine("打麻将啦。。");
        }
    }


    public class Japanese : ISports
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public void Hi()
        {
            Console.WriteLine("Hi");
        }
        public void Pingpang()
        {
            Console.WriteLine("打乒乓球...");
        }
    }
}

在Main()方法里面实例化:

People people = new People()
{
        Id = 123,
        Name = "走自己的路"
};
Chinese chinese = new Chinese()
{
        Id = 234,
        Name = "晴天"
};
Hubei hubei = new Hubei(123)
{
        Id = 345,
        Name = "流年"
};
Japanese japanese = new Japanese()
{
        Id = 7654,
        Name = "werwer"
};

这时有一个需求:需要打印出Id和Name属性的值,将ShowObject()方法修改如下:
AS31EQ.png
但是这样修改报错了:object类里面没有Id和Name属性,可能会有人说,强制类型转换一下就行了啊:

public static void ShowObject(object oParameter)
{
         Console.WriteLine("This is {0},parameter={1},type={2}",
         typeof(CommonMethod), oParameter.GetType().Name, oParameter);

         Console.WriteLine($"{((People)oParameter).Id}_{((People)oParameter).Name}");
}

这样修改以后,代码不会报错了,这时我们在Main()方法里面调用:

CommonMethod.ShowObject(people);
CommonMethod.ShowObject(chinese);
CommonMethod.ShowObject(hubei);
CommonMethod.ShowObject(japanese);

结果:

可以看出程序报错了,因为Japanese没有继承自People,这里类型转换的时候失败了。这样会造成类型不安全的问题。那么怎么解决类型不安全的问题呢?那就是使用泛型约束。

所谓的泛型约束,实际上就是约束的类型T。使T必须遵循一定的规则。比如T必须继承自某个类,或者T必须实现某个接口等等。那么怎么给泛型指定约束?其实也很简单,只需要where关键字,加上约束的条件。

泛型约束总共有五种。

约束说明
T:结构类型参数必须是值类型
T:类类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。
T:new()类型参数必须具有无参数的公共构造函数。 当与其他约束一起使用时,new() 约束必须最后指定。
T:<基类名>类型参数必须是指定的基类或派生自指定的基类。
T:<接口名称>类型参数必须是指定的接口或实现指定的接口。 可以指定多个接口约束。 约束接口也可以是泛型的。
  1. 基类约束

上面打印的方法约束T类型必须是People类型。

/// <summary>
/// 基类约束:约束T必须是People类型或者是People的子类
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tParameter"></param>
public static void Show<T>(T tParameter) where T : People
{
      Console.WriteLine($"{tParameter.Id}_{tParameter.Name}");
      tParameter.Hi();
}

注意:

基类约束时,基类不能是密封类,即不能是sealed类。sealed类表示该类不能被继承,在这里用作约束就无任何意义,因为sealed类没有子类。

  1. 接口约束
/// <summary>
/// 接口约束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static T Get<T>(T t) where T : ISports
{
      t.Pingpang();
      return t;
}
  1. 引用类型约束 class

引用类型约束保证T一定是引用类型的。

/// <summary>
/// 引用类型约束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static T Get<T>(T t) where T : class
{
      return t;
}
  1. 值类型约束 struct

值类型约束保证T一定是值类型的。

/// <summary>
/// 值类型类型约束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static T Get<T>(T t) where T : struct
{
      return t;
}

5.无参数构造函数约束 new()

/// <summary>
/// new()约束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static T Get<T>(T t) where T : new()
{
     return t;
}

泛型约束也可以同时约束多个,例如:

public static void Show<T>(T tParameter)
            where T : People, ISports, IWork,
            new()
{
      Console.WriteLine($"{tParameter.Id}_{tParameter.Name}");
      tParameter.Hi();
      tParameter.Pingpang();
      tParameter.Work();
}

注意:有多个泛型约束时,new()约束一定是在最后。

六、泛型的协变和逆变

协变和逆变是在.NET 4.0的时候出现的,只能放在接口或者委托的泛型参数前面,out 协变covariant,用来修饰返回值;in:逆变contravariant,用来修饰传入参数。

先看下面的一个例子:

定义一个Animal类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
    public class Animal
    {
        public int Id { get; set; }
    }
}

然后在定义一个Cat类继承自Animal类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
    public class Cat :Animal
    {
        public string Name { get; set; }
    }
}

在Main()方法可以这样调用:

// 直接声明Animal类
Animal animal = new Animal();
// 直接声明Cat类
Cat cat = new Cat();
// 声明子类对象指向父类
Animal animal2 = new Cat();
// 声明Animal类的集合
List<Animal> listAnimal = new List<Animal>();
// 声明Cat类的集合
List<Cat> listCat = new List<Cat>();

那么问题来了:下面的一句代码是不是正确的呢?

List<Animal> list = new List<Cat>();

可能有人会认为是正确的:因为一只Cat属于Animal,那么一群Cat也应该属于Animal啊。但是实际上这样声明是错误的:因为List和List之间没有父子关系。

在这里插入图片描述

这时就可以用到协变和逆变了。

// 协变
IEnumerable<Animal> List1 = new List<Animal>();
IEnumerable<Animal> List2 = new List<Cat>();

F12查看定义:
在这里插入图片描述

可以看到,在泛型接口的T前面有一个out关键字修饰,而且T只能是返回值类型,不能作为参数类型,这就是协变。使用了协变以后,左边声明的是基类,右边可以声明基类或者基类的子类。

协变除了可以用在接口上面,也可以用在委托上面:

Func<Animal> func = new Func<Cat>(() => null);

除了使用.NET框架定义好的以为,我们还可以自定义协变,例如:

/// <summary>
/// out 协变 只能是返回结果
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ICustomerListOut<out T>
{
     T Get();
}

public class CustomerListOut<T> : ICustomerListOut<T>
{
     public T Get()
     {
         return default(T);
     }
}

使用自定义的协变:

// 使用自定义协变
ICustomerListOut<Animal> customerList1 = new CustomerListOut<Animal>();
ICustomerListOut<Animal> customerList2 = new CustomerListOut<Cat>();

在来看看逆变。

在泛型接口的T前面有一个In关键字修饰,而且T只能方法参数,不能作为返回值类型,这就是逆变。请看下面的自定义逆变:

/// <summary>
/// 逆变 只能是方法参数
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ICustomerListIn<in T>
{
     void Show(T t);
}

public class CustomerListIn<T> : ICustomerListIn<T>
{
     public void Show(T t)
     {
     }
}

使用自定义逆变:

// 使用自定义逆变
ICustomerListIn<Cat> customerListCat1 = new CustomerListIn<Cat>();
ICustomerListIn<Cat> customerListCat2 = new CustomerListIn<Animal>();

协变和逆变也可以同时使用,看看下面的例子:

/// <summary>
/// inT 逆变
/// outT 协变
/// </summary>
/// <typeparam name="inT"></typeparam>
/// <typeparam name="outT"></typeparam>
public interface IMyList<in inT, out outT>
{
     void Show(inT t);
     outT Get();
     outT Do(inT t);
}

public class MyList<T1, T2> : IMyList<T1, T2>
{

     public void Show(T1 t)
     {
          Console.WriteLine(t.GetType().Name);
     }

     public T2 Get()
     {
          Console.WriteLine(typeof(T2).Name);
          return default(T2);
      }

      public T2 Do(T1 t)
      {
           Console.WriteLine(t.GetType().Name);
           Console.WriteLine(typeof(T2).Name);
           return default(T2);
       }
 }

使用:

IMyList<Cat, Animal> myList1 = new MyList<Cat, Animal>();
IMyList<Cat, Animal> myList2 = new MyList<Cat, Cat>();//协变
IMyList<Cat, Animal> myList3 = new MyList<Animal, Animal>();//逆变
IMyList<Cat, Animal> myList4 = new MyList<Animal, Cat>();//逆变+协变

七、泛型缓存

在前面我们学习过,类中的静态类型无论实例化多少次,在内存中只会有一个。静态构造函数只会执行一次。在泛型类中,T类型不同,每个不同的T类型,都会产生一个不同的副本,所以会产生不同的静态属性、不同的静态构造函数,请看下面的例子:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
    public class GenericCache<T>
    {
        static GenericCache()
        {
            Console.WriteLine("This is GenericCache 静态构造函数");
            _TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff"));
        }

        private static string _TypeTime = "";

        public static string GetCache()
        {
            return _TypeTime;
        }
    }
}

然后新建一个测试类,用来测试GenericCache类的执行顺序:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MyGeneric
{
    public class GenericCacheTest
    {
        public static void Show()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(GenericCache<int>.GetCache());
                Thread.Sleep(10);
                Console.WriteLine(GenericCache<long>.GetCache());
                Thread.Sleep(10);
                Console.WriteLine(GenericCache<DateTime>.GetCache());
                Thread.Sleep(10);
                Console.WriteLine(GenericCache<string>.GetCache());
                Thread.Sleep(10);
                Console.WriteLine(GenericCache<GenericCacheTest>.GetCache());
                Thread.Sleep(10);
            }
        }
    }
}

Main()方法里面调用:

GenericCacheTest.Show();

结果:
在这里插入图片描述
从上面的截图中可以看出,泛型会为不同的类型都创建一个副本,所以静态构造函数会执行5次。 而且每次静态属性的值都是一样的。利用泛型的这一特性,可以实现缓存。

注意:只能为不同的类型缓存一次。泛型缓存比字典缓存效率高。泛型缓存不能主动释放

委托 delegate

一、委托

1、什么是委托

委托是面向对象的、类型安全的,是引用类型。使用delegate关键字进行定义。委托的本质就是一个类,继承自System.MulticastDelegate,里面内置了几个方法 ,可以在类的外面声明委托,也可以在类的内部声明委托。

对委托的使用:先定义,后声明和实例化委托,然后作为参数传递给方法。

1.1 定义委托

下面是几种委托定义的例子:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyDelegateDemo
{
    // 也可以在类的外面定义委托
    public delegate void NoReturnNoParaOutClass();

    public class MyDelegate
    {
        // 声明无参数无返回值的泛型委托
        public delegate void NoReturnNoPara<T>(T t);
        // 声明无参数无返回值的委托
        public delegate void NoReturnNoPara();
        // 声明有参数无返回值的委托
        public delegate void NoReturnWithPara(int x, int y);
        // 声明无参数有返回值的委托
        public delegate int WithReturnNoPara();
        // 声明有参数有返回值的委托
        public delegate string WithReturnWithPara(out int x,ref int y);
    }
}
1.2 声明并实例化委托

实例化委托时参数传递的是一个方法,方法的签名必须和委托的签名一样(即方法的返回值类型、参数列表的参数类型都必须和定义的委托一致)。

// 委托的实例化,DoNothing是一个方法
// NoReturnNoPara是定义的无参无返回值的委托,所以DoNothing方法也必须是无参无返回值的
NoReturnNoPara method = new NoReturnNoPara(DoNothing);

DoNothing()方法定义如下:

private void DoNothing()
{
    Console.WriteLine("This is DoNothing");
}
1.3 委托实例的调用
// 调用委托
method.Invoke();
// Invoke也可以去掉
method();

注意:委托的调用和直接执行方法的效果是一样的,例如:

// 调用委托
method.Invoke();
// Invoke也可以去掉
method();
// 直接执行方法
this.DoNothing();

在控制台的Main()方法里面,结果如下:
在这里插入图片描述

从截图中能够看出:三种方式的输出结果都是一样的。

讲到这里可能有人会问:既然使用委托和直接调用方法的效果是一样的,那为什么还要使用委托呢,直接调用方法多么简单?下面先来看一个实际的例子。

先定义一个Student类,里面有一些属性和方法,Student类定义如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyDelegateDemo
{
    public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int ClassId { get; set; }
        public int Age { get; set; }

        public static void Show()
        {
            Console.WriteLine("123");
        }
    }
}

然后使用集合初始化器的方式初始化一个List集合,填充一些测试数据:

private List<Student> GetStudentList()
{
            #region 初始化数据
            List<Student> studentList = new List<Student>()
            {
                new Student()
                {
                    Id=1,
                    Name="老K",
                    ClassId=2,
                    Age=35
                },
                new Student()
                {
                    Id=1,
                    Name="hao",
                    ClassId=2,
                    Age=23
                },
                 new Student()
                {
                    Id=1,
                    Name="大水",
                    ClassId=2,
                    Age=27
                },
                 new Student()
                {
                    Id=1,
                    Name="半醉人间",
                    ClassId=2,
                    Age=26
                },
                new Student()
                {
                    Id=1,
                    Name="风尘浪子",
                    ClassId=2,
                    Age=25
                },
                new Student()
                {
                    Id=1,
                    Name="一大锅鱼",
                    ClassId=2,
                    Age=24
                },
                new Student()
                {
                    Id=1,
                    Name="小白",
                    ClassId=2,
                    Age=21
                },
                 new Student()
                {
                    Id=1,
                    Name="yoyo",
                    ClassId=2,
                    Age=22
                },
                 new Student()
                {
                    Id=1,
                    Name="冰亮",
                    ClassId=2,
                    Age=34
                },
                 new Student()
                {
                    Id=1,
                    Name="瀚",
                    ClassId=2,
                    Age=30
                },
                new Student()
                {
                    Id=1,
                    Name="毕帆",
                    ClassId=2,
                    Age=30
                },
                new Student()
                {
                    Id=1,
                    Name="一点半",
                    ClassId=2,
                    Age=30
                },
                new Student()
                {
                    Id=1,
                    Name="小石头",
                    ClassId=2,
                    Age=28
                },
                new Student()
                {
                    Id=1,
                    Name="大海",
                    ClassId=2,
                    Age=30
                },
                 new Student()
                {
                    Id=3,
                    Name="yoyo",
                    ClassId=3,
                    Age=30
                },
                  new Student()
                {
                    Id=4,
                    Name="unknown",
                    ClassId=4,
                    Age=30
                }
            };
            #endregion
            return studentList;
}

现在有一个需求,找出List集合里面年龄大于25的学生信息,代码如下:

List<Student> studentList = this.GetStudentList();
//找出年龄大于25
List<Student> resultAge = new List<Student>();//准备容器
foreach (Student student in studentList)//遍历数据源
{
      if (student.Age > 25)//判断条件
      {
           resultAge.Add(student);//满足条件的放入容器
      }
}
Console.WriteLine($"结果一共有{resultAge.Count()}个");

使用一个foreach循环很容易得到全部年纪大于25的学生,这时又提出了需求:找出name长度大于2的学生、找出Name长度大于2 而且年龄大于25 而且班级id是2的学生,代码如下:

//找出Name长度大于2
List<Student> resultName = new List<Student>();
foreach (Student student in studentList)
{
       if (student.Name.Length > 2)
       {
              resultName.Add(student);
       }
}
Console.WriteLine($"结果一共有{resultName.Count()}个");

//找出Name长度大于2 而且年龄大于25 而且班级id是2
List<Student> result = new List<Student>();
foreach (Student student in studentList)
{
        if (student.Name.Length > 2 && student.Age > 25 && student.ClassId == 2)
        {
            result.Add(student);
        }
}
Console.WriteLine($"结果一共有{result.Count()}个");

观察上面的代码,你会发现里面有很多重复的代码:每次都要先准备一个查询结果集的集合,然后遍历数据源,判断条件,把满足条件的放入到集合中。可不可以把上面的代码进行优化呢?请看下面的代码:

private List<Student> GetList(List<Student> source, int type)
{
            List<Student> result = new List<Student>();
            foreach (Student student in source)
            {
                switch(type)
                {
                    case 1:
                        if (student.Age > 25)
                        {
                            result.Add(student);
                        }
                        break;
                    case 2:
                        if (student.Name.Length > 2)
                        {
                            result.Add(student);
                        }
                        break;
                    case 3:
                        if (student.Name.Length > 2 && student.Age > 25 && student.ClassId == 2)
                        {
                            result.Add(student);
                        }
                        break;
                }
            }
            return result;
}

在上面这段代码中,每次根据不同的type类型执行不同的判断条件,这样看起来可以把一些重复的代码进行了重用,但是这样又会有其他的问题:所有的判断逻辑都写在了一起,如果又增加了一种type类型或者判断逻辑改变了,就要修改整个代码,违反了开闭原则。

仔细观察上面的这段代码:GetList()方法需要传入一个int类型的参数,根据不同的参数,执行对应的逻辑。那么可不可以直接传递逻辑进来呢?逻辑就是方法,也就是说能不能传递一个方法进来。可能有人会问题,方法都是进行调用啊,怎么能进行传递呢?答案是肯定的:那就是使用上面讲到的委托。

以查询年龄大于25的学生为例:逻辑就是判断学生的年龄是否大于25,返回一个bool值,如果大于25就添加到集合中,根据逻辑,可以得到下面的方法:

private bool Than(Student student)
{
     return student.Age > 25;
}

根据这个方法的签名可以定义如下的委托:

// 定义委托
public delegate bool ThanDelegate(Student student);

修改上面GetList的方法,把委托作为参数传递进来:

private List<Student> GetListDelegate(List<Student> source, ThanDelegate method)
{
       List<Student> result = new List<Student>();
       foreach (Student student in source)
       {
             // 调用委托
             if (method.Invoke(student))
             {
                 result.Add(student);
              }
        }
        return result;
}

实例化委托:

// 实例化委托
ThanDelegate method = new ThanDelegate(this.Than);
List<Student> resultDele = this.GetListDelegate(studentList, method);
Console.WriteLine($"结果一共有{resultDele.Count()}个");

另外两个可以定义如下的方法:

/// <summary>
/// 查询Name长度大于2
/// </summary>
/// <param name="student"></param>
/// <returns></returns>
private bool LengthThan(Student student)
{
      return student.Name.Length > 2;
}

/// <summary>
/// 查询Name长度大于2 而且年龄大于25 而且班级id是2
/// </summary>
/// <param name="student"></param>
/// <returns></returns>
private bool AllThan(Student student)
{
      return student.Name.Length > 2 && student.Age > 25 && student.ClassId == 2;
}

实例化委托如下:

//Name长度大于2
ThanDelegate nameMethod = new ThanDelegate(LengthThan);
List<Student> nameList= this.GetListDelegate(studentList, nameMethod);
Console.WriteLine($"Name长达大于2的结果一共有{nameList.Count()}个");

//Name长度大于2 而且年龄大于25 而且班级id是2
ThanDelegate allMethod = new ThanDelegate(AllThan);
List<Student> allList = this.GetListDelegate(studentList, allMethod);
Console.WriteLine($"Name长度大于2 而且年龄大于25 而且班级id是2的结果一共有{nameList.Count()}个");

观察GetListDelegate这个方法:保留了以前公用的代码:准备一个结果集的集合、循环遍历数据源,把符合条件的学生添加到结果集中,而判断逻辑放到了单独的一个方法中,如果判断逻辑改变了或者需要增加新的判断逻辑,只需要修改原有的判断逻辑或者新增判断逻辑即可,这样可以做到不需要修改GetListDelegate()这个方法,很好的符合开不原则。

可以总结出委托的一个应用:委托可以解除公用逻辑(准备结果集的集合、循环遍历数据源,添加到结果集中)和具体的业务逻辑(例如判断年龄大于25)的耦合,可以减少重复的代码。

2、多种途径实例化委托

委托实例化的时候不仅可以传入当前类型的普通方法,还可以传入静态、实例方法等,例如:

// 传入当前类型的普通方法
NoReturnNoPara method = new NoReturnNoPara(DoNothing);
// 传入当前类型的静态方法
NoReturnNoPara methodStatic = new NoReturnNoPara(DoNothingStatic);
// 传入其他类型的静态方法
NoReturnNoPara methodOtherStaitc = new NoReturnNoPara(Student.StudyAdvanced);
// 传入其他类型的普通方法
NoReturnNoPara methodOther = new NoReturnNoPara(new Student().Study);

其中DoNothingStatic()方法定义如下:

private void DoNothingStatic()
{
     Console.WriteLine("This is DoNothingStatic");
}

Student类的静态方法和实例方法定义如下:

public static void StudyAdvanced()
{
      Console.WriteLine("欢迎学习高级班课程");
}

public void Study()
{
      Console.WriteLine("学习");
}

总结:实例化委托时传入的方法只有一个要求:方法的签名和委托的签名一样,即返回值类型和参数列表一致,无论该方法来自于当前类型的普通方法、静态方法或者其他类型的实例方法和静态方法。

3、多播委托

像上面实例化委托的时候,一个委托类型的变量只能保存一个方法,使用多播委托,一个委托类型的变量可以保存多个方法,多播委托可以增加、减少委托,Invoke的时候可以按顺序执行。

+= 为委托实例按顺序增加方法,形成方法链,Invoke时,按顺序依次执行,例如下面的代码:

// 实例化委托
NoReturnNoPara method = new NoReturnNoPara(DoNothing);
method += new NoReturnNoPara(this.DoNothing);
method += new NoReturnNoPara(DoNothingStatic);
method += new NoReturnNoPara(Student.StudyAdvanced);
method += new NoReturnNoPara(new Student().Study);
method.Invoke();

+=委托的最后输出结果是什么是?请看下面的截图:
在这里插入图片描述
从上面的截图中可以看出:多播委托的执行结果是把所有传入的方法都执行一遍,而且是按照实例化时传入方法的顺序依次执行的。(上面传入了两次当前类型的DoNoThing方法,所以会执行两边。)

-= 为委托实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合的,移除且只移除一个,没有也不异常。

method -= new NoReturnNoPara(this.DoNothing);
method -= new NoReturnNoPara(DoNothingStatic);
method -= new NoReturnNoPara(Student.StudyAdvanced);
method -= new NoReturnNoPara(new Student().Study);
method.Invoke();

移除委托的执行结果是什么呢?在上面添加委托的时候传入了5个方法,移除委托的时候移除了4个方法,应该只会执行DoNothing()这一个方法,是这样的吗?看看下面的运行结果:
在这里插入图片描述

从截图中可以看出,最后的结果和我们猜测的结果不同,除了执行DoNothing()方法以外,还执行了Study()方法,但是添加的时候我们只添加了一个Study()方法,而且后面又移除掉了,那为什么还会执行这个方法呢?原因是因为添加和移除时候不是同一个实例的Study()方法(添加和移除的时候都是new了一个新实例),所以移除的时候不会被移除掉。怎么证明上面的原因是否正确呢?请看下面的代码:

Console.WriteLine("***多播委托添加方法***");
// 实例化一个Student对象
Student student = new Student();
// 实例化委托
NoReturnNoPara method = new NoReturnNoPara(DoNothing);
method += new NoReturnNoPara(this.DoNothing);
method += new NoReturnNoPara(DoNothingStatic);
method += new NoReturnNoPara(Student.StudyAdvanced);
method += new NoReturnNoPara(new Student().Study);
method += new NoReturnNoPara(student.Study);
method.Invoke();

Console.WriteLine("***下面是多播委托移除方法***");
//-= 为委托实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合的,移除且只移除一个,没有也不异常
method -= new NoReturnNoPara(this.DoNothing);
method -= new NoReturnNoPara(DoNothingStatic);
method -= new NoReturnNoPara(Student.StudyAdvanced);//不是同一个实例,所以是不同的方法
method -= new NoReturnNoPara(student.Study);
method.Invoke();

查看运行结果:
在这里插入图片描述

从运行结果中可以看出上面的原因是正确的。

注意:多播委托不能异步调用(即调用BeginInvoke()),因为多播委托里面有很多方法,异步调用的时候不知道该怎样执行,是把所有方法同步执行呢还是按照顺序依次执行呢,BeginInvoke不知道该如何调用,所以多播委托不能直接调用BeginInvoke()方法。那如果我想使用该怎么办呢?可以使用GetInvocationList()方法,F12查看GetInvocationList()方法的定义:
在这里插入图片描述

那么可以使用如下的代码:

foreach(NoReturnNoPara item in method.GetInvocationList())
{
      item.Invoke();
}

上面的多播委托例子中一直都是使用的没有返回值的委托,如果是有返回值的委托,那么返回值是什么呢?请看下面的例子:

先定义几个有返回值的方法:

private int GetSomething()
{
       return 1;
}
private int GetSomething2()
{
       return 2;
}
private int GetSomething3()
{
        return 3;
}

实例化委托:

WithReturnNoPara methodWithReturn = new WithReturnNoPara(this.GetSomething);
methodWithReturn += new WithReturnNoPara(this.GetSomething2);
methodWithReturn += new WithReturnNoPara(this.GetSomething3);
int iResult = methodWithReturn.Invoke();
Console.WriteLine("返回值:"+iResult.ToString());

运行程序查看结果:
ASWiS1.md.png

从截图中可以看出:带返回值的多播委托的结果是最后添加的方法的返回值。中间方法的返回值都会被丢弃。

总结:多播委托一般用来调用无返回值的方法,不用来调用有返回值的方法,因为有返回值的多播委托中间的结果都会被丢弃掉。

二、事件

1、什么是事件

事件是带event关键字的委托的实例。

2、如何声明事件

// 声明委托
public delegate void MiaoDelegate();
// 声明事件
public event MiaoDelegate MiaoDelegateHandlerEvent;

3、委托和事件的区别和联系

委托是一个类型,例如Student类。

事件是委托类型的一个实例,例如具体的一个学生。

4、为什么要是有事件

事件不能直接执行Invoke()方法,可以限制变量被外部调用或者直接赋值。

注意:即使是在子类中,事件也不能调用Invoke()方法。

三、委托和事件的应用

来看下面的一个例子:

有一个Cat类,里面有一个Miao()的方法,猫叫了一声,然后触发一系列的后续动作,通常的实现代码如下:

public void Miao()
{
     Console.WriteLine("{0} Miao", this.GetType().Name);

      new Mouse().Run();
      new Baby().Cry();
      new Mother().Wispher();
      new Father().Roar();
      new Neighbor().Awake();
      new Stealer().Hide();
      new Dog().Wang();
}

调用Miao()方法:

// 实例化
Cat cat = new Cat();
cat.Miao();

上面的代码可以实现上述的需求,但是这段代码耦合性很强,因为是在Miao()方法里面直接调用别的实例的方法,以后无论是增加或者修改、调整方法的调用顺序,都要修改Miao()方法,使得Miao()方法不稳定。

下面使用委托来优化上面的代码:

// 声明委托
public delegate void MiaoDelegate();
public MiaoDelegate MiaoDelegateHandler;
public void MiaoNew()
{
     Console.WriteLine("{0} MiaoNew", this.GetType().Name);
     if (this.MiaoDelegateHandler != null)
     {
          this.MiaoDelegateHandler.Invoke();
     }
}

调用:

Cat cat = new Cat();
// 多播委托
cat.MiaoDelegateHandler += new MiaoDelegate(new Mouse().Run);
cat.MiaoDelegateHandler += new MiaoDelegate(new Baby().Cry);
cat.MiaoDelegateHandler += new MiaoDelegate(new Mother().Wispher);
cat.MiaoDelegateHandler += new MiaoDelegate(new Brother().Turn);
cat.MiaoDelegateHandler += new MiaoDelegate(new Father().Roar);
cat.MiaoDelegateHandler += new MiaoDelegate(new Neighbor().Awake);
cat.MiaoDelegateHandler += new MiaoDelegate(new Stealer().Hide);
cat.MiaoDelegateHandler += new MiaoDelegate(new Dog().Wang);
cat.MiaoNew();

上面的委托也可以改为事件实现:

// 声明事件
public event MiaoDelegate MiaoDelegateHandlerEvent;
public void MiaoNewEvent()
{
     Console.WriteLine("{0} MiaoNewEvent", this.GetType().Name);
     if (this.MiaoDelegateHandlerEvent != null)
     {
          this.MiaoDelegateHandlerEvent.Invoke();
     }
}

调用:

Cat cat = new Cat();
cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Mouse().Run);
cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Baby().Cry);
cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Mother().Wispher);
cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Brother().Turn);
cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Father().Roar);
cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Neighbor().Awake);
cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Stealer().Hide);
cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Dog().Wang);
cat.MiaoNewEvent();

C#内置泛型委托:Action委托

1、什么是Action泛型委托

Action是.NET Framework内置的泛型委托,可以使用Action委托以参数形式传递方法,而不用显示声明自定义的委托。封装的方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有一个通过值传递给它的参数,并且不能有返回值。

2、Action委托定义

查看Action的定义:

using System.Runtime.CompilerServices;

namespace System
{
    //
    // 摘要:
    //     封装一个方法,该方法不具有参数且不返回值。
    [TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")]
    public delegate void Action();
}

你会发现,Action其实就是没有返回值的delegate。

3、示例

Action委托至少0个参数,至多16个参数,无返回值。

Action 表示无参,无返回值的委托。

Action<int,string> 表示有传入参数int,string无返回值的委托。

Action<int,string,bool> 表示有传入参数int,string,bool无返回值的委托。

Action<int,int,int,int> 表示有传入4个int型参数,无返回值的委托。
在这里插入图片描述

代码示例如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ActionDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 无参数无返回值的委托
            Action action1 = new Action(ActionWithNoParaNoReturn);
            action1();
            Console.WriteLine("----------------------------");
            // 使用delegate
            Action action2 = delegate { Console.WriteLine("这里是使用delegate"); };
            // 执行
            action2();
            Console.WriteLine("----------------------------");
            // 使用匿名委托
            Action action3 = () => { Console.WriteLine("这里是匿名委托"); };
            action3();
            Console.WriteLine("----------------------------");
            // 有参数无返回值的委托
            Action<int> action4 = new Action<int>(ActionWithPara);
            action4(23);
            Console.WriteLine("----------------------------");
            // 使用delegate
            Action<int> action5 = delegate (int i) { Console.WriteLine($"这里是使用delegate的委托,参数值是:{i}"); };
            action5(45);
            Console.WriteLine("----------------------------");
            // 使用匿名委托
            Action<string> action6 = (string s) => { Console.WriteLine($"这里是使用匿名委托,参数值是:{s}"); };
            action6("345");
            Console.WriteLine("----------------------------");
            // 多个参数无返回值的委托
            Action<int, string> action7 = new Action<int, string>(ActionWithMulitPara);
            action7(7, "abc");
            Console.WriteLine("----------------------------");
            // 使用delegate
            Action<int, int, string> action8 = delegate (int i1, int i2, string s) 
            {
                Console.WriteLine($"这里是三个参数的Action委托,参数1的值是:{i1},参数2的值是:{i2},参数3的值是:{s}");
            };
            action8(12, 34, "abc");
            Console.WriteLine("----------------------------");
            Action<int,int,string, string> action9 = (int i1,int i2, string s1,string s2) => 
            {
                Console.WriteLine($"这里是使用四个参数的委托,参数1的值是:{i1},参数2的值是:{i2},参数3的值是:{s1},参数4的值是:{s2}");
            };
            // 执行委托
            action9(34,56, "abc","def");
            Console.ReadKey();
        }




        static void ActionWithNoParaNoReturn()
        {
            Console.WriteLine("这是无参数无返回值的Action委托");
        }

        static void ActionWithPara(int i)
        {
            Console.WriteLine($"这里是有参数无返回值的委托,参数值是:{i}");
        }

        static void ActionWithMulitPara(int i,string s)
        {
            Console.WriteLine($"这里是有两个参数无返回值的委托,参数1的值是:{i},参数2的值是:{s}");
        }
    }
}

运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7aomxSJB-1581865397038)(https://s2.ax1x.com/2019/03/09/AS5zGQ.md.png)]

4、真实示例

先看下面一张截图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y1xc3qll-1581865397038)(https://s2.ax1x.com/2019/03/09/ASIS2j.md.png)]

从截图中可以看出:ForEach()方法的参数是一个参数类型是T的无返回值的Action委托,下面的示例中利用Action委托作为参数传递给ForEach()方法。

1、定义Student实体类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ActionDemo
{
    public class Student
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public int Age { get; set; }

        public int Sex { get; set; }
    }
}
2、利用ForEach()方法输出集合内容
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ActionDemo
{
    public class ActionTest
    {
        public static void Test()
        {
            List<Student> list = new List<Student>()
            {
              new Student(){Id=1,Name="张三",Age=19,Sex=1},
              new Student(){Id=2,Name="李四",Age=20,Sex=2},
              new Student(){Id=3,Name="王五",Age=23,Sex=1},
              new Student(){Id=4,Name="赵六",Age=18,Sex=1}
            };

            // Action<Student>委托作为参数传递给ForEach()方法
            list.ForEach(student =>
            {
                Console.WriteLine($"姓名:{student.Name},年龄:{student.Age}");
            });
        }
    }
}
3、在Main()方法中调用
ActionTest.Test();
4、结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-viEfD0Hd-1581865397038)(https://s2.ax1x.com/2019/03/09/ASIEIU.md.png)]

C#内置泛型委托:Func委托

1、什么是Func委托

Func委托代表有返回类型的委托

2、Func委托定义

查看Func的定义:

using System.Runtime.CompilerServices;

namespace System
{
    //
    // 摘要:
    //     封装一个方法,该方法具有两个参数,并返回由 TResult 参数指定的类型的值。
    //
    // 参数:
    //   arg1:
    //     此委托封装的方法的第一个参数。
    //
    //   arg2:
    //     此委托封装的方法的第二个参数。
    //
    // 类型参数:
    //   T1:
    //     此委托封装的方法的第一个参数的类型。
    //
    //   T2:
    //     此委托封装的方法的第二个参数的类型。
    //
    //   TResult:
    //     此委托封装的方法的返回值类型。
    //
    // 返回结果:
    //     此委托封装的方法的返回值。
    [TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")]
    public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
}

你会发现,Func其实就是有多个输出参数并且有返回值的delegate。

3、示例

Func至少0个输入参数,至多16个输入参数,根据返回值泛型返回。必须有返回值,不可void。

Func 表示没有输入参参,返回值为int类型的委托。

Func<object,string,int> 表示传入参数为object, string ,返回值为int类型的委托。

Func<object,string,int> 表示传入参数为object, string, 返回值为int类型的委托。

Func<T1,T2,T3,int> 表示传入参数为T1,T2,T3(泛型),返回值为int类型的委托。
在这里插入图片描述

代码示例如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FunDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 无参数,只要返回值 
            Func<int> fun1 = new Func<int>(FunWithNoPara);
            int result1= fun1();
            Console.WriteLine(result1);
            Console.WriteLine("----------------------------");
            Func<int> fun2 = delegate { return 19; };
            int result2 = fun2();
            Console.WriteLine(result2);
            Console.WriteLine("----------------------------");
            Func<int> fun3 = () => { return 3; };
            int result3 = fun3();
            Console.WriteLine(result3);
            Console.WriteLine("----------------------------");
            //有一个参数,一个返回值
            Func<int, int> fun4 = new Func<int, int>(FunWithPara);
            int result4 = fun4(4);
            Console.WriteLine($"这里是一个参数一个返回值的方法,返回值是:{result4}");
            Console.WriteLine("----------------------------");
            // 使用委托
            Func<int, string> fun5 = delegate (int i) { return i.ToString(); };
            string result5 = fun5(5);
            Console.WriteLine($"这里是一个参数一个返回值的委托,返回值是:{result5}");
            Console.WriteLine("----------------------------");
            // 使用匿名委托
            Func<int, string> fun6 = (int i) => 
            {
                return i.ToString();
            };
            string result6 = fun6(6);
            Console.WriteLine($"这里是一个参数一个返回值的匿名委托,返回值是:{result6}");
            Console.WriteLine("----------------------------");
            // 多个输入参数
            Func<int, string, bool> fun7 = new Func<int, string, bool>(FunWithMultiPara);
            bool result7 = fun7(2, "2");
            Console.WriteLine($"这里是有多个输入参数的方法,返回值是:{result7}");
            Console.WriteLine("----------------------------");
            // 使用委托
            Func<int, string, bool> fun8 = delegate (int i, string s) 
            {
                return i.ToString().Equals(s) ? true : false;
            };
            bool result8 = fun8(2, "abc");
            Console.WriteLine($"这里是有多个输入参数的委托,返回值是:{result8}");
            Console.WriteLine("----------------------------");
            // 使用匿名委托
            Func<int, string, bool> fun9 = (int i, string s) => 
            {
                return i.ToString().Equals(s) ? true : false;
            };
            bool result9 = fun9(45, "ert");
            Console.WriteLine($"这里是有多个输入参数的匿名委托,返回值是:{result9}");
            Console.ReadKey();

        }

        static int FunWithNoPara()
        {
            return 10;
        }

        static int FunWithPara(int i)
        {
            return i;
        }

        static bool FunWithMultiPara(int i,string s)
        {
            return i.ToString().Equals(s) ? true : false;
        }
    }
}

运行结果:
在这里插入图片描述

4、真实示例

在下面的示例中,利用Func委托封装数据库通用访问类。

1、定义BaseModel基类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FunApplication.Model
{
    public class BaseModel
    {
        public int Id { get; set; }
    }
}
2、定义Student类继承自BaseModel基类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FunApplication.Model
{
    public class Student : BaseModel
    {
        public string Name { get; set; }

        public int Age { get; set; }

        public int Sex { get; set; }

        public string Email { get; set; }
    }
}
3、定义数据库访问方法接口
using FunApplication.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FunApplication.IDAL
{
    public interface IBaseDAL
    {
        T Query<T>(int id) where T : BaseModel;

        List<T> QueryAll<T>() where T : BaseModel;

        int Insert<T>(T t) where T : BaseModel;

        int Update<T>(T t) where T : BaseModel;

        int Delete<T>(int id) where T : BaseModel;
    }
}
4、定义属性帮助类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace FunApplication.AttributeExtend
{
    public static class AttributeHelper
    {
        public static string GetColumnName(this PropertyInfo prop)
        {
            if (prop.IsDefined(typeof(ColumnAttribute), true))
            {
                ColumnAttribute attribute = (ColumnAttribute)prop.GetCustomAttribute(typeof(ColumnAttribute), true);
                return attribute.GetColumnName();
            }
            else
            {
                return prop.Name;
            }
        }
    }
}
5、定义ColumnAttribute类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FunApplication.AttributeExtend
{
    [AttributeUsage(AttributeTargets.Property)]
    public class ColumnAttribute : Attribute
    {
        public ColumnAttribute(string name)
        {
            this._Name = name;
        }

        private string _Name = null;
        public string GetColumnName()
        {
            return this._Name;
        }
    }
}
6、定义数据库方法接口实现类
using FunApplication.IDAL;
using FunApplication.Model;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using FunApplication.AttributeExtend;

namespace FunApplication.DAL
{
    public  class BaseDAL : IBaseDAL
    {
        // 数据库链接字符串
        private static string strConn = ConfigurationManager.ConnectionStrings["DbConnection"].ConnectionString;
        public  int Delete<T>(int id) where T : BaseModel
        {
            int result = 0;

            using (SqlConnection conn = new SqlConnection(strConn))
            {
                string strSQL = "delete from Student where Id=@Id";
                SqlParameter para = new SqlParameter("Id", id);
                SqlCommand command = new SqlCommand(strSQL, conn);
                command.Parameters.Add(para);
                conn.Open();
                result = command.ExecuteNonQuery();
            }
            return result;
        }

        public int Insert<T>(T t) where T : BaseModel
        {
            int result = 0;
            using (SqlConnection conn = new SqlConnection(strConn))
            {
                Type type = typeof(T);
                var propArray = type.GetProperties().Where(p => p.Name != "Id");
                string strSQL = "insert into Student Values (@Name,@Age,@Sex,@Email) ";
                SqlCommand command = new SqlCommand(strSQL, conn);
                var parameters = propArray.Select(p => new SqlParameter($"@{p.GetColumnName()}", p.GetValue(t) ?? DBNull.Value)).ToArray();
                command.Parameters.AddRange(parameters);
                conn.Open();
                result = command.ExecuteNonQuery();
            }
                return result;
        }

        public T Query<T>(int id) where T : BaseModel
        {
            Type type = typeof(T);
            string columnString = string.Join(",", type.GetProperties().Select(p => $"[{p.GetColumnName()}]"));
            string sql = $"SELECT {columnString} FROM [{type.Name}] WHERE Id={id}";
            T t = null;// (T)Activator.CreateInstance(type);

            using (SqlConnection conn = new SqlConnection(strConn))
            {
                SqlCommand command = new SqlCommand(sql, conn);
                conn.Open();
                SqlDataReader reader = command.ExecuteReader();
                List<T> list = this.ReaderToList<T>(reader);
                t = list.FirstOrDefault();          
            }
            return t;
        }

        public List<T> QueryAll<T>() where T : BaseModel
        {
            Type type = typeof(T);
            string columnString = string.Join(",", type.GetProperties().Select(p => $"[{p.GetColumnName()}]"));
            string sql = $"SELECT {columnString} FROM [{type.Name}] ";
            List<T> list = new List<T>();
            using (SqlConnection conn = new SqlConnection(strConn))
            {
                SqlCommand command = new SqlCommand(sql, conn);
                conn.Open();
                SqlDataReader reader = command.ExecuteReader();
                list = this.ReaderToList<T>(reader);
            }
            return list;
        }

        public int Update<T>(T t) where T : BaseModel
        {
            int result = 0;
            using (SqlConnection conn = new SqlConnection(strConn))
            {
                Type type = typeof(T);
                var propArray = type.GetProperties().Where(p => p.Name != "Id");
                string columnString = string.Join(",", propArray.Select(p => $"[{p.GetColumnName()}]=@{p.GetColumnName()}"));
                var parameters = propArray.Select(p => new SqlParameter($"@{p.GetColumnName()}", p.GetValue(t) ?? DBNull.Value)).ToArray();                
                //必须参数化  否则引号?  或者值里面还有引号
                string strSQL = $"UPDATE [{type.Name}] SET {columnString} WHERE Id={t.Id}";
                SqlCommand command = new SqlCommand(strSQL, conn);
                command.Parameters.AddRange(parameters);
                conn.Open();
                result = command.ExecuteNonQuery();            
            }
            return result;
        }

        private List<T> ReaderToList<T>(SqlDataReader reader) where T : BaseModel
        {
            Type type = typeof(T);
            List<T> list = new List<T>();
            while (reader.Read())//表示有数据  开始读
            {
                T t = (T)Activator.CreateInstance(type);
                foreach (var prop in type.GetProperties())
                {
                    object oValue = reader[prop.GetColumnName()];
                    if (oValue is DBNull)
                        oValue = null;
                    prop.SetValue(t, oValue);//除了guid和枚举
                }
                list.Add(t);
            }
            return list;
        }
    }
}
7、在Main()方法中调用
using FunApplication.DAL;
using FunApplication.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FunApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            #region MyRegion
            BaseDAL dal = new BaseDAL();
            // 查询
            Student student = dal.Query<Student>(2);
            Console.WriteLine($"姓名:{student.Name},年龄:{student.Age},Email地址:{student.Email}");
            Console.WriteLine("----------------------------");
            // 查询所有
            List<Student> list = dal.QueryAll<Student>();
            Console.WriteLine($"集合个数:{list.Count}");
            Console.WriteLine("----------------------------");
            // 插入
            Student studentIns = new Student()
            {
                Name = "小明",
                Age = 20,
                Sex = 2,
                Email = "xiaoming@qq.com"
            };
            bool resultIns = dal.Insert<Student>(studentIns) > 0 ? true : false;
            Console.WriteLine($"插入执行结果:{resultIns}");
            Console.WriteLine("----------------------------");
            // 更新
            Student studentUpd = new Student()
            {
                Id = 1,
                Name = "zhangsan1234",
                Age = 20,
                Sex = 2,
                Email = "zhangsan1234@qq.com"
            };
            bool resultUpd = dal.Update<Student>(studentUpd) > 0 ? true : false;
            Console.WriteLine($"更新执行结果:{resultUpd}");
            Console.WriteLine("----------------------------");
            // 删除
            bool resultDel = dal.Delete<Student>(3) > 0 ? true : false;
            Console.WriteLine($"删除执行结果:{resultDel}");
            #endregion
            Console.ReadKey();
        }
    }
}
8、结果

在这里插入图片描述

9、优化

仔细观察上面步骤7中的代码,你会发现在每个方法中都有重复的代码,打开链接,执行SqlCommand命令,那么这些重复的代码能不能提取到一个公共的方法中进行调用呢?答案是可以的,那就是利用Func委托,看下面优化后的代码:

using FunApplication.AttributeExtend;
using FunApplication.IDAL;
using FunApplication.Model;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace FunApplication.DAL
{
    public class FunBaseDAL : IBaseDAL
    {
        // 数据库链接字符串
        private static string strConn = ConfigurationManager.ConnectionStrings["DbConnection"].ConnectionString;

        public int Delete<T>(int id) where T : BaseModel
        {
            Type type = typeof(T);
            string sql = $"delete from {type.Name} where Id=@Id";
            Func<SqlCommand, int> func = (SqlCommand command) => 
            {               
                SqlParameter para = new SqlParameter("Id", id);
                command.Parameters.Add(para);
                return command.ExecuteNonQuery();
            };

            return ExcuteSql<int>(sql, func);
        }

        public int Insert<T>(T t) where T : BaseModel
        {
            int result = 0;
            Type type = typeof(T);
            var propArray = type.GetProperties().Where(p => p.Name != "Id");
            string strSQL = "insert into Student Values (@Name,@Age,@Sex,@Email) ";
            var parameters = propArray.Select(p => new SqlParameter($"@{p.GetColumnName()}", p.GetValue(t) ?? DBNull.Value)).ToArray();
            Func<SqlCommand, int> func = (SqlCommand command) => 
            {
                command.Parameters.AddRange(parameters);
                return command.ExecuteNonQuery();
            };
            result = ExcuteSql<int>(strSQL, func);
            return result;
        }

        public T Query<T>(int id) where T : BaseModel
        {
            Type type = typeof(T);
            string columnString = string.Join(",", type.GetProperties().Select(p => $"[{p.GetColumnName()}]"));
            string sql = $"SELECT {columnString} FROM [{type.Name}] WHERE Id=@Id";
            T t = null;
            DataTable dt = new DataTable();
            
            Func<SqlCommand, T> func = (SqlCommand command) => 
            {
                SqlParameter para = new SqlParameter("@Id", id);
                command.Parameters.Add(para);
                SqlDataAdapter adapter = new SqlDataAdapter(command);
                //SqlDataReader reader = command.ExecuteReader();
                //List<T> list = this.ReaderToList<T>(reader);
                adapter.Fill(dt);
                List<T> list = ConvertToList<T>(dt);
                T tResult = list.FirstOrDefault();
                return tResult;
            };
            t = ExcuteSql<T>(sql, func);
            return t;
        }

        public List<T> QueryAll<T>() where T : BaseModel
        {
            Type type = typeof(T);
            string columnString = string.Join(",", type.GetProperties().Select(p => $"[{p.GetColumnName()}]"));
            string sql = $"SELECT {columnString} FROM [{type.Name}] ";
            T t = null;

            Func<SqlCommand, List<T>> func = (SqlCommand command) =>
            {
                SqlDataReader reader = command.ExecuteReader();
                List<T> list = this.ReaderToList<T>(reader);
                return list;
            };
            return ExcuteSql<List<T>>(sql, func);
        }

        public int Update<T>(T t) where T : BaseModel
        {
            int result = 0;
            Type type = typeof(T);
            var propArray = type.GetProperties().Where(p => p.Name != "Id");
            string columnString = string.Join(",", propArray.Select(p => $"[{p.GetColumnName()}]=@{p.GetColumnName()}"));
            var parameters = propArray.Select(p => new SqlParameter($"@{p.GetColumnName()}", p.GetValue(t) ?? DBNull.Value)).ToArray();
            //必须参数化  否则引号?  或者值里面还有引号
            string strSQL = $"UPDATE [{type.Name}] SET {columnString} WHERE Id={t.Id}";
            Func<SqlCommand, int> func = (SqlCommand command) => 
            {
                command.Parameters.AddRange(parameters);
                return command.ExecuteNonQuery();
            };
            result = ExcuteSql<int>(strSQL, func);
            return result;
        }


        //多个方法里面重复对数据库的访问  想通过委托解耦,去掉重复代码
        private T ExcuteSql<T>(string sql, Func<SqlCommand, T> func)
        {
            using (SqlConnection conn = new SqlConnection(strConn))
            {
                using (SqlCommand command = new SqlCommand(sql, conn))
                {
                    conn.Open();
                    SqlTransaction sqlTransaction = conn.BeginTransaction();
                    try
                    {
                        command.Transaction = sqlTransaction;
                        T tResult = func.Invoke(command);
                        sqlTransaction.Commit();
                        return tResult;
                    }
                    catch (Exception ex)
                    {
                        sqlTransaction.Rollback();
                        throw;
                    }
                }
            }
        }

        private List<T> ReaderToList<T>(SqlDataReader reader) where T : BaseModel
        {
            Type type = typeof(T);
            List<T> list = new List<T>();
            while (reader.Read())//表示有数据  开始读
            {
                T t = (T)Activator.CreateInstance(type);
                foreach (var prop in type.GetProperties())
                {
                    object oValue = reader[prop.GetColumnName()];
                    if (oValue is DBNull)
                        oValue = null;
                    prop.SetValue(t, oValue);//除了guid和枚举
                }
                list.Add(t);
            }
            reader.Close();
            return list;
        }
    }
}
10、在Main()方法中调用
using FunApplication.DAL;
using FunApplication.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FunApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            #region 传统实现
            //BaseDAL dal = new BaseDAL();
             查询
            //Student student = dal.Query<Student>(2);
            //Console.WriteLine($"姓名:{student.Name},年龄:{student.Age},Email地址:{student.Email}");
            //Console.WriteLine("----------------------------");
             查询所有
            //List<Student> list = dal.QueryAll<Student>();
            //Console.WriteLine($"集合个数:{list.Count}");
            //Console.WriteLine("----------------------------");
             插入
            //Student studentIns = new Student()
            //{
            //    Name = "小明",
            //    Age = 20,
            //    Sex = 2,
            //    Email = "xiaoming@qq.com"
            //};
            //bool resultIns = dal.Insert<Student>(studentIns) > 0 ? true : false;
            //Console.WriteLine($"插入执行结果:{resultIns}");
            //Console.WriteLine("----------------------------");
             更新
            //Student studentUpd = new Student()
            //{
            //    Id = 1,
            //    Name = "zhangsan1234",
            //    Age = 20,
            //    Sex = 2,
            //    Email = "zhangsan1234@qq.com"
            //};
            //bool resultUpd = dal.Update<Student>(studentUpd) > 1 ? true : false;
            //Console.WriteLine($"更新执行结果:{resultUpd}");
            //Console.WriteLine("----------------------------");
             删除
            //bool resultDel = dal.Delete<Student>(5) > 1 ? true : false;
            //Console.WriteLine($"删除执行结果:{resultDel}");
            #endregion

            #region 利用委托
            // 查询
            FunBaseDAL dal = new FunBaseDAL();
            Student student = dal.Query<Student>(1);
            Console.WriteLine($"姓名:{student.Name},年龄:{student.Age},Email地址:{student.Email}");
            Console.WriteLine("----------------------------");
            // 查询所有
            List<Student> list = dal.QueryAll<Student>();
            Console.WriteLine($"集合个数:{list.Count}");
            Console.WriteLine("----------------------------");
            // 插入
            Student studentIns = new Student()
            {
                Name = "tom",
                Age = 19,
                Sex = 1,
                Email = "tom@163.com"
            };
            bool resultIns = dal.Insert<Student>(studentIns) > 0 ? true : false;
            Console.WriteLine($"插入执行结果:{resultIns}");
            Console.WriteLine("----------------------------");
            List<Student> list1 = dal.QueryAll<Student>();
            Console.WriteLine($"插入后集合个数:{list1.Count}");
            Console.WriteLine("----------------------------");
            // 更新
            Student studentUpd = new Student()
            {
                Id = 2,
                Name = "马六123",
                Age = 20,
                Sex = 2,
                Email = "maliu1234@qq.com"
            };
            bool resultUpd = dal.Update<Student>(studentUpd) > 0 ? true : false;
            Console.WriteLine($"更新执行结果:{resultUpd}");
            Console.WriteLine("----------------------------");
            // 删除
            bool resultDel = dal.Delete<Student>(8) > 0 ? true : false;
            Console.WriteLine($"删除执行结果:{resultDel}");
            List<Student> list2 = dal.QueryAll<Student>();
            Console.WriteLine($"删除后集合个数:{list2.Count}");
            Console.WriteLine("----------------------------");
            #endregion
            Console.ReadKey();
        }
    }
}
11、结果

在这里插入图片描述

注意

在使用SqlDataReader的时候有时会报错:“已有打开的与此Command相关联的DataReader,必须先将它关闭”。

同时打开两个或循环多个sqldatareader会出现以上错误。因为用的是sqldatareader做数据库的数据读取,sqlconnection开启没有关闭。

一个SqlConnection只能执行一次事务,没用一次必须关闭然后再开启。上面我只用了一次没有关闭,直接开启所以会报错。解决方案有如下两种:

1、其实不用多次打开在开启,那样实现起来很麻烦。直接在连接字符串的后面加上MultipleActiveResultSets=true即可。 配置文件定义如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <!--<add name="DbConnection" connectionString="Server=.;Initial Catalog=MyDb;User ID=sa;Password=123456;MultipleActiveResultSets=True"/>-->
    <!--配置文件里面添加MultipleActiveResultSets=True-->
    <add name="DbConnection" connectionString="Server=.;Initial Catalog=MyDb;User ID=sa;Password=123456;MultipleActiveResultSets=True"/>
  </connectionStrings>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
</configuration>
2、使用DataTable

在上面是使用的SqlDataReader读取数据,然后转换成List,可以用DataTable代替SqlDataReader,这样就不会报错了,代码如下:

/// <summary>
/// 将DataTable转换成List
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dt"></param>
/// <returns></returns>
private List<T> ConvertToList<T>(DataTable dt) where T:BaseModel
{
      Type type = typeof(T);
      List<T> list = new List<T>();
      foreach(DataRow dr in dt.Rows)
      {
          T t = (T)Activator.CreateInstance(type);
          foreach(PropertyInfo prop in type.GetProperties())
          {
               object value = dr[prop.GetColumnName()];
               if(value is DBNull)
               {
                    value = null;
               }
               prop.SetValue(t, value);
          }
          list.Add(t);
        }
        return list;
}

转载自:博客园–.NET开发菜鸟

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值