委托和泛型

委托

委托类似C++的函数指针,但是函数指针不安全。C#中的委托就是一个新类,每次定义的时候都等于声明一个类。可以这么理解:委托类中储存函数元数据(返回值,参数,函数名),初始化的过程就是给函数具体的内容(就是存的内容是函数指针)。

  • 定义和初始化委托

      public delegate string GetString();
      class Program
      {
      	static void Main(string[] args)
      	{
      		var a = 10;
      		GetString getString = new GetString(a.ToString);
      		Console.WriteLine(getString());
      	}
      }
    

调用实例方法时,可以调用实例的属性。

  • 匿名委托
    委托中需要储存的有返回值和参数,考虑用泛型:
    Action<T>void,返回值由T来定
    Fuction<T>eg:Fuction<int,int,bool>最后一个参数为返回值
  • 参数修饰符 in out ref
    假设一个顾客要衣物工厂生产衣服
  1. in

     public delegate Cloth Produce(in ISource _source)
    

    函数的含义就是顾客给原材料,厂商生产衣服,但是厂商不能动顾客的原材料。顾客给的是什么,执行函数后还是原样。

  2. out

     public delegate void Produce(in ISource _source,out Cloth _cloth)
    

    函数的含义就是顾客不仅给原材料,还会给一个衣服。至于是什么样的衣服,或者是根本就没有衣服,厂商不仅不能动原材料,还要产出衣服给out参数。

  3. ref

     public delegate void Produce(in ISource _source,ref ISource _otherSource,out Cloth _cloth)
    

    函数的含义就是顾客给两种原材料,in的不能动,ref的可以修改,返回Cloth

  • 多播委托
    委托重构了+、+=、-、-=。但是调用函数指针的顺序却没有定,且一个函数抛出异常整个迭代过程都会停止。

      GetString getString1=()=>Console.WriteLine("nihao");
      GetString getString2 = () => Console.WriteLine("woshi");
      GetString getString3 = () => Console.WriteLine("robot");
      GetString getString = getString1 + getString2 + getString3;
      getString();
    

    可以用Delegate类中的GetInvocationList()

      Delegate[] delegates = getString.GetInvocationList();
      foreach (GetString i in delegates)
      {
      	try
      	{
      		i();
      	}
      	catch (Exception e)
      	{
      		Console.WriteLine("wrong");
      	}
      }
    

泛型

泛型没什么好说的,首先要了解两个概念:1.值类型与引用类型。2.装箱与拆箱。

  1. 值类型与引用类型
    C#和Java一样万物都是类,即使是值类型也是继承自ValueType->Object。但是编译器会对继承自ValueType的类特殊处理。值类型储存在栈上,引用类型储存在堆上。值类型是一般意义的非面向对象的数据结构,仅仅是服务于计算。引用类型服务于整个项目工程。

  2. 装箱与拆箱

     int i = 10;
     object j = i;
     Console.WriteLine(i + " " + j);
    

    考虑上述语句:虽然输出值是一样的,但i是值类型,存于栈中;j是引用类型,存于堆中。
    把值类型转化为引用类型的操作就是装箱,反正为拆箱。从栈到堆要重新申请地址填写元数据,等于就是又生成了一个变量,增大了性能开销。
    为了解决这种浪费问题,可以通过构造函数、自定义函数传参。但是这样的话,针对不同类型的变量就要写许许多多函数。泛型就是用来解决代码重复问题,也是loc控制反转、di依赖注入思想的体现。

  • 泛型类
  1. 继承

     public class Student<T> : Person<T>
    

    和一般类的继承是一样的,但是泛型的继承要求T是相同的类。还有种指定父类泛型的继承:

     public class Student<T1> : Person<T1,string>
    

    注意泛型个数的变化。可以理解为每一种不同T的泛型都是不同的,独立的类。比如静态属性:

  2. 静态属性

     Student<int>.i = 10;
     Student<string>.i= 6;
     Console.WriteLine(Student<int>.i);
    

    互相独立无法影响。
    C#中无法访问属性一样,通过实例访问静态属性,因为静态属性是属于整个类的。

  3. 默认值

     public T name=default(T);
    

    值类型为0,引用为null

  • 泛型方法
    前两种准确来说并不是泛型方法
  1. 返回值是泛型

     public Person<T> GetPerson()
    
  2. 参数是泛型

     public class Student<T> : Person<T>
     {
     	static int j;
     	public Person<T> GetPerson(T a)
     	{
     		var temp = new Person<T>();
     		temp.name=a;
     		return temp;
     	}
     }
    

    还是一个原则T要相同。

  3. 泛型方法

     public Person<T> GetPerson<T>()
     {
         var temp = new Person<T>();
         return temp;
     }
    

    泛型方法的目的也是为了减少代码重用。不同的泛型类调用不同的方法

  • 泛型接口
  1. 协变与抗变
    首先在看协变与抗变之前先回顾类与子类:

    Student<string> s = new Student<string>();
    Person<string> p = s;
    
    Person<string> i = new Person<string>();
    Student<string> j = i;
    

    前一种可以编译通过,因为可以把子类的地址赋给父类的变量。但反过来就不可以了。这就是一种简单的协变与抗变。因为子类是从父类派生的。
    方法也有协变与抗变:方法的参数是协变的,可以给父类参数赋子类对象的值,这也是依赖注入的实现方式。方法的返回值是抗变的,返回值最好是返回精确的类型。输入可以不精确,但是输出一定要确定。

     public interface IMyInterface<out T>
    

    这样定义的泛型类就可以对参数协变
    public interface IMyInterface
    这样的

  • 泛型结构
    最常用的泛型结构就是可空类型Nullable
上一篇:斐波那契查找算法


下一篇:lamp