zoukankan      html  css  js  c++  java
  • 01.泛型Generic

    1. 基本了解

    1.1 什么是泛型?

    字面意思:不确定的类型

    泛型常用:泛型方法,泛型类,泛型接口,泛型委托

    1.2 泛型 T(熟悉)

    T 的作用,其实就是一个通用的容器,制造它的人开始不指定它是用来装什么的,而使用者在使用它的时候要告诉这个容器准备用来装什么,容器知道了用来装什么之后,后面所有存入操作,它都要检查一下你放的东西是不是开始指定的东西类型

    所谓泛型,即通过参数化类型来实现在同一份代码上操作多种数据类型

    泛型允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候,换句话说,泛型允许您编写一个可以与任何数据类型一起工作的类或方法

    泛型编程是一种编程范式,它利用“参数化类型”将类型抽象化,从而实现更为灵活的复用。在定义泛型类时,在对客户端代码能够在实例化类时,可以用类型参数的类型种类施加限制

    原理:泛型的使用是来源于c#2.0新出的规则和框架的升级,对原生需求的变更,泛型不是语法糖,是应对数据类型在传递参数的时候解决多代码冗余问题,减少代码的重复和可维护性。泛型的协变与逆变和泛型约束在c#4.0出现,解决c#出现的代码父类继承问题

    1.3 设计思想

    泛型的思想表现了一个很重要的架构思想: 延迟思想,推迟一切可以推迟的,使程序有更多的灵活性和扩展性,用来解决,方法中是相同的操作,但是传入参数是不同类型的问题(例举)

    1.4 应用场景

    类型不明确时:自定义对象的时候,如果我们会定义很多类似的对象,之后参数类型不同,那么我们此时可以考虑在定义对象的时候使用泛型

    定义变量,定义方法的参数,定义方法的返回值

    示例:返回结果

    public class Result<T>
    {
        public int code { get; set; }
        public List<T> date { get; set; }
    }
        
    Result<A> result_a = new Result<A>() { code = 200, date = new List<A>() };
    Result<B> result_b = new Result<B>() { code = 200, date = new List<B>() };
    

    1.5 装箱拆箱(了解)

    在没有泛型之前,用 object 类型也可以实现相同操作,但是会有些性能损耗及类型安全问题

    说明

    简单来说,装箱是将值类型转换为引用类型 ;拆箱是将引用类型转换为值类型

    装箱:用于在垃圾回收堆中存储值类型。装箱是值类型到 object 类型或到此值类型所实现的任何接口类型的隐式转换

    拆箱:从 object 类型到值类型或从接口类型到实现该接口的值类型的显式转换

    c#类型

    C#中值类型和引用类型的最终基类都是Object类型(它本身是一个引用类型)。也就是说,值类型也可以当做引用类型来处理。而这种机制的底层处理就是通过装箱和拆箱的方式来进行,利用装箱和拆箱功能,可通过允许值类型的任何值与Object 类型的值相互转换,将值类型与引用类型链接起来

    发生场景

    一种最普通的场景是,调用一个含类型为Object的参数的方法,该Object可支持任意为型,以便通用。当你需要将一个值类型(如Int32)传入时,需要装箱。

    另一种用法是,一个非泛型的容器,同样是为了保证通用,而将元素类型定义为Object。于是,要将值类型数据加入容器时,需要装箱

    装箱和拆箱的内部操作

    .NET中数据类型划分为值类型和引用类型,与此对应,内存分配被分成了两种方式,一为栈,二为堆(托管堆)

    值类型只会在栈中分配,引用类型分配内存与托管堆(托管堆对应于垃圾回收)

    2. 泛型方法

    泛型方法可以定义特定于其执行范围的泛型参数

    2.1 定义泛型方法

    public class MyClass
    {
        // 指定MyMethod方法用以执行类型为X的参数
        public void MyMethod<X>(X x) 
        {
            //
        }
    
        //此方法也可不指定方法参数
        public void MyMethod<X>() 
        {
            //
        }
    }
    

    2.2 调用泛型方法

    MyClass mycls = new MyClass();
    mycls.MyMethod<int>(3);
    

    3. 泛型类

    类级别泛型参数的所有约束都必须在类作用范围中定义

    3.1 定义泛型类

    简单定义

    public class MyC<T>
    {
        ...
    }
    
    public class MyC<T>
    {
        public T Get(){...}
        public void Show(T t){...}
    }
    

    普通类继承泛型类

    public class MyClass<T>:MyC<T>
    {
        ...
    }
    

    泛型类继承泛型类,继承的泛型类型必须是可推断的(与子类一致或者一个具体的类型)

    public class MyClass2<T>:MyC<int>
    {
        ...
    }
    

    3.2 其它示例

    示例一

    public class MyC<T> where T:new()
    {
        public void Show<T>(){T entity}
    }
    
    public class MyClass<T> where T:IComparable<T>
    {
        public void MyMethod<X>(X x,T t)
        {
            //
        }
    }
    

    4. 泛型接口

    4.1 定义泛型接口

    简单定义

    public interface MyInterface<T>
    {
    	...
    }
    
    public interface MyInterface<T>
    {
    	T Get();
        void Show(T t);
    }
    

    泛型接口继承接口

    public interface MyInterface2<T>:MyInterface<T>
    {
    	...
    }
    

    5. 泛型委托

    在某个类中定义的委托可以使用该类的泛型参数

    5.1 定义泛型委托

    示例一

    public class MyClass<T>
    {
        public delegate void GenericDelegate(T t);
        public void SomeMethod(T t)
        {
     
        }
    }
    

    5.2 使用泛型委托

    示例一:同定义示例一

    public GenericMethodDemo()
    {
        MyClass<int> obj = new MyClass<int>();
        MyClass<int>.GenericDelegate del;
        del = new MyClass<int>.GenericDelegate(obj.SomeMethod);
        del(3);
    }
    

    6. 泛型约束

    6.1 类型安全问题

    show 方法若使用 object 类型参数,虽然编译器中没有报错,但是在运行中会出现类型转换失败问题,原因是类型 C 并没有继承 A 父类,此处就引发了类型错误问题,而泛型约束就是解决类型安全问题

    namespace t1
    {
        class Program
        {
            static void Main(string[] args)
            {
                A obja = new A() { id = 1, name = "a" };
                B objb = new B() { id = 2, name = "b" };
                C objc = new C() { id = 3, name = "C" };
    
                try
                {
                    Show(obja);
                    Show(objb);
                    Show(objc);
                }
                catch (Exception ex)
                {
    
                }
            }
    
            static void Show(object oval)
            {
                A obj = (A)oval;
                Console.WriteLine(obj.id);
            }
        }
    
        public class A
        {
            public int id { get; set; }
            public string name { get; set; }
        }
    
        public class B : A
        {
    
        }
    
        public class C
        {
            public int id { get; set; }
            public string name { get; set; }
        }
    
    }
    

    6.2 常用约束列表

    约束 说明
    where T:基类名 类型参数必须是指定的基类或派生自指定的基类
    where T:接口名称 类型参数必须是指定的接口或实现指定的接口
    where T:class 类型参数必须是引用类型,包括任何类、接口、委托或数组类型
    where T:struct 类型参数必须是值类型,可以指定除 Nullable 以外的任何值类型
    where T:new() 类型参数必须具有无参数的公共构造函数,与其他约束同使用时,必须最后指定

    6.3 常用示例

    示例一:接口约束|派生约束

    // 1.常见
    public class MyGenericClass<T> where T:IComparable { }
    
    // 2.约束放在类的实际派生之后
    public class B { }
    public class MyClass6<T> : B where T : IComparable { }
    
    // 3.可以继承一个基类和多个接口,且基类在接口前面
    public class B { }
    public class MyClass7<T> where T : B, IComparable, ICloneable { }
    

    示例二:引用类型,值类型约束

    public class c<T> where T:class
    
    public class MyClassy<T, U> where T : class where U : struct
    {
    }
    

    构造函数约束

    以使用 new 运算符创建类型参数的实例;但类型参数为此必须受构造函数约束 new() 的约束。new() 约束可以让编译器知道:提供的任何类型参数都必须具有可访问的无参数(或默认)构造函数。new() 约束出现在 where 子句的最后

    // 1.常见的
    public class MyClass8<T> where T :  new() { }
    
    // 2.可以将构造函数约束和派生约束组合起来,前提是构造函数约束出现在约束列表的最后
    public class MyClass8<T> where T : IComparable, new() { }
    

    7. 泛型缓存

    泛型缓存,使用泛型类+静态字段,根据不同类型的“T”会被即时编译为不同的类型从而实现缓存
    个人理解,T 的作用是一个类型模板,静态字段本身就是线程唯一的,使用泛型时,指定类型从而生成这个类型的副本,从而这个类型中的静态内容也就生成了一份

    7.1 示例一:简单示例

    定义泛型类,缓存内容使用静态变量并在第一次执行(使用)时缓存此类型(T)内容

    public class GenericClass<T>
    {
        private static string GenericCache = null;
        static GenericClass() // 静态构造函数,在泛型类第一次传入具体的类型进来的时候,执行
        {
            GenericCache = $"{typeof(T).Name}-{DateTime.Now.ToLocalTime()}";
        }
        public static string GetData()
        {
            return GenericCache;
        }
    }
    

    测试使用,测试不同类型,或同一类型多次调用

    static void Main(string[] args)
    {
        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine(GenericClass<string>.GetData());
            Thread.Sleep(1000);
            Console.WriteLine(GenericClass<int>.GetData());
            Thread.Sleep(1000);
        }
    }
    

    7.2 示例一:简单简版

    public class GenericClass<T>
    {
        // 定义为静态只读变量
        public static readonly string GenericCache = null;
        static GenericClass() // 静态构造函数,在泛型类第一次传入具体的类型进来的时候,执行
        {
            GenericCache = $"{typeof(T).Name}	{DateTime.Now.ToLocalTime()}";
        }
    }
    
    static void Main(string[] args)
    {
        for (int i = 0; i < 3; i++)
        {
            // 只能获取,不能赋值
            Console.WriteLine(GenericClass<string>.GenericCache);
            Thread.Sleep(1000);
            Console.WriteLine(GenericClass<int>.GenericCache);
            Thread.Sleep(1000);
        }
    }
    

    7.3 示例二:实际示例

    定义泛型类,用于实现缓存指定SQL语句

    public class GenericSql<T> where T : class
    {
        public static readonly string FindSql = null;
        public static readonly string DeleteSql = null;
        public static readonly string FindAllSql = null;
        public static readonly string UpdateSql = null;
        static GenericSql()
        {
            Type type = typeof(T);
            var props = type.GetProperties().Select(x => $"{x.Name}");
    
            FindSql = $"select {string.Join(",", props.Select(x => $"[{x}]"))} from [{type.Name}] where id=@id";
            
            DeleteSql = $"delete from [{type.Name}] where id=@id";
            
            FindAllSql = $"select {string.Join(",", props.Select(x => $"[{x}]"))} from [{type.Name}]";
            
            UpdateSql = $"update [{type.Name}] set {string.Join(",", props.Where(x => !x.Equals("id")).Select(x => $"[{x}]=@{x}"))} where id=@id";
        }
    }
    

    测试使用,使用两个实体类测试

    public class Role
    {
        public int rid { get; set; }
        public string rname { get; set; }
    }
    public class User
    {
        public int uid { get; set; }
        public string uname { get; set; }
    }
    
    static void Main(string[] args)
    {
        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine(GenericSql<Role>.FindSql);
            Console.WriteLine(GenericSql<User>.FindSql);
    
            Console.WriteLine(GenericSql<Role>.DeleteSql);
            Console.WriteLine(GenericSql<User>.DeleteSql);
    
            Console.WriteLine(GenericSql<Role>.FindAllSql);
            Console.WriteLine(GenericSql<User>.FindAllSql);
    
            Console.WriteLine(GenericSql<Role>.UpdateSql);
            Console.WriteLine(GenericSql<User>.UpdateSql);
        }
    }
    

    8. 协变逆变(扩展)

    协变和逆变只有在泛型接口,泛型委托中才有,协变逆变也可以组合使用

    8.1 使用问题

    // 鸟类
    public class Bird
    {
        public int id { get; set; }
    }
    
    // 麻雀类
    public class Sparrow:Bird
    {
        public string name { get; set; }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            // 麻雀也属于鸟类
            Bird bird1 = new Bird();
            Bird bird2 = new Sparrow();
    
            // 从人类语言上来说,一组麻雀也是一组鸟类
            // 但是在程序中,List<Bird> 是一个新的类,与 List<Sparrow> 无父子关系
            // List<Bird> list = new List<Sparrow>(); 报错
        }
    }
    

    8.2 协变

    协变:使用 out 修饰类型参数,且类型参数只能用作返回值,不可用于输入参数,使得子类可在右边

    namespace t2
    {
        // 鸟类
        public class Bird
        {
            public int id { get; set; }
        }
    
        // 麻雀类
        public class Sparrow : Bird
        {
            public string name { get; set; }
        }
    
        // 自定义协变
        public interface ICustomerListOut<out T>
        {
            T Get();
            //Show(T t);
        }
    
        public class CustomerListOut<T> : ICustomerListOut<T>
        {
            public T Get()
            {
                return default(T);
            }
    
            //public void Show(T t) { }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                // 协变
                IEnumerable<Bird> birds1 = new List<Sparrow>();
    			
                // 自定义
                ICustomerListOut<Bird> birds2 = new CustomerListOut<Sparrow>();
            }
        }
    }
    

    8.3 逆变

    逆变:使用 in 修饰类型参数,且类型参数只能用作输入参数,不可用于输入参数返回值,使得父类可在右边

    namespace t2
    {
        // 鸟类
        public class Bird
        {
            public int id { get; set; }
        }
    
        // 麻雀类
        public class Sparrow : Bird
        {
            public string name { get; set; }
        }
    
        public interface ICustomerListIn<in T>
        {
            // T Get();
    
            void Show(T t);
        }
    
        public class CustomerListIn<T> : ICustomerListIn<T>
        {
            //public T Get()
            //{
            //    return default(T);
            //}
    
            public void Show(T t) { ... }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                // 逆变
                ICustomerListIn<Sparrow> sparrow1 = new CustomerListIn<Bird>();
            }
        }
    }
    

    8.4 个人理解(不做参考)

    协变:子类向父类转换

    逆变:父类向子类转换

    到达胜利之前无法回头!
  • 相关阅读:
    苏州是新的一线城市拦路虎?
    SAP MM ME57界面看到的供应源跟Source List主数据不一致?
    SAP QM 检验批里样品数量的确定
    SAP QM 检验批里某检验特性的取样数量跟检验计划设置不符?
    SAP QM 主检验特性主数据关键字段解释
    SAP S4HANA TR传输之操作
    SAP QA32试图做UD,系统报错-工厂 BTYC中的 QM 基选设置需要维护
    使用 boost.asio 简单实现 异步Socket 通信
    fedora 28/29 配置 C++ 环境
    gitlab 和 github 配置 SSH Keys
  • 原文地址:https://www.cnblogs.com/weiyongguang/p/15087069.html
Copyright © 2011-2022 走看看