.NET学习笔记(一)----泛型的引入、优势、设计思想、原理、应用

本文深入探讨了泛型在编程中的重要性,包括其引入的原因、优势和与普通类型的差异。通过代码示例展示了泛型方法的声明与调用,解释了泛型在编译器和运行时的支持方式。此外,文章还涵盖了泛型在方法、接口、类和委托中的应用,并详细阐述了泛型约束的各种类型,如基类约束、接口约束等。最后,讨论了泛型缓存的概念以及泛型的协变和逆变特性,解释了它们在实际编程中的作用和应用场景。
摘要由CSDN通过智能技术生成

脑图:

在这里插入图片描述

一、泛型的引入,优势

1.泛型是一种宽泛的、不确定的类型
2.它无处不在
3.与普通类型的差异:
泛型与普通类型的区别
4.支持传递多类型的参数类型
在这里插入图片描述

PS:在一个方法中对一个对象进行强制转换【转换为People people =
(People)传入参数】,如果方法的传入参数为Object类型,那么给他传入Chinese类型的参数编译时不会报错,但是运行时会发生异常,而如果为泛型方法的话直接在方法中强转传入参数的类型的话会发生编译报错。

代码示例:

/// <summary>
/// object  :引用类型
/// </summary>
/// <param name="oParameter"></param>
public static void ShowObject(object oParameter)
{
    Console.WriteLine($"This is {typeof(CommonMethod).Name},parameter={oParameter.GetType().Name},type={oParameter}");
}


/// <summary>
/// 泛型方法
/// </summary>
/// <typeparam name="T">类型变量  参数类型</typeparam>
/// <param name="tParameter"></param>
public static void Show<T>(T tParameter)
{
    Console.WriteLine($"This is {typeof(CommonMethod).Name},parameter={tParameter.GetType().Name},type={tParameter}");
}

PS:

引用类型----声明后存储到托管堆
值类型----存储到线程栈

二、泛型的声明与调用

1、声明
①、泛型方法:

–在方法名称后面多了一个尖括号,尖括号中有占位符
例如:Show<T>(T Value)

②、延迟声明:

–声明的时候,只是给一个占位符T,T为不确定类型。
–调用的时候,指定具体类型,调用(使用)的时候,必须要确定类型,你说什么就是什么;

③、占位符(不可使用关键字):

T—类型参数—类型变量

④、类型参数当做方法的参数的时候,用于明确参数类型
在这里插入图片描述

⑤、尖括号中可存在多个类型参数:

public class Test<T,X,S>

2.调用
①、调用时需要多一个<>,尖括号中指定的类型要和传递的参数类型一致。如:

Show<string>(“value”)
Show<int>(123)

②、如果可以通过参数推导出类型—尖括号可以省略。如:

Show("value")
Show(123)

三、泛型的特点+原理----在底层如何支持

①、在高级语言中,定义的泛型T,在计算机执行的执行的时候,一定要是一个具体的类型

②、在底层如何支持?
在底层看到 生成的结果是:
代码:

Console.WriteLine(typeof(int));

输出结果:
在这里插入图片描述
代码:

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

输出结果:
在这里插入图片描述
在底层—生成了带有`1`2 `3…`4.`5…

③、编译器必须要能够支持泛型

----C# 2.0 .NET Framework 2.0 Visual Studio 2005 开始支持

④、CLR 运行时环境也必须要支持泛型

⑤、泛型是由框架升级支持的

----.net中泛型不是语法糖,但是在调用时可以省略类型属于语法糖
----语法糖:编译器提供的便捷功能

四、泛型的多种应用

①.泛型方法----可以一个方法满足不同类型的需求

/// <summary>
/// 泛型方法
/// </summary>
/// <typeparam name="T">类型变量  参数类型</typeparam>
/// <param name="tParameter"></param>
public static void Show<T>(T tParameter)
{
    Console.WriteLine($"This is {typeof(CommonMethod).Name},parameter={tParameter.GetType().Name},type={tParameter}");
}

//调用
Show<string>("value");
Show<DateTime>(DateTime.Now);

②.泛型接口----可以一个接口满足不同类型的需求

/// <summary>
/// 泛型接口
/// </summary>
/// <typeparam name="T"></typeparam>
public interface GenericInterfac<T>
{
}

//调用
GenericInterfac<string> sgenericInterfac = null;
GenericInterfac<DateTime> dtgenericInterfac = null;

③.泛型类------可以一个类型满足不同类型的需求

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

//调用
GenericClass<string> sGenericClass = new GenericClass<string>();
GenericClass<DateTime> dtsGenericClass = new GenericClass<DateTime>();

④.泛型委托----可以一个委托满足不同类型的需求

/// <summary>
/// 泛型委托
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public delegate T Genericdelegate<T>();

//调用
Genericdelegate<string> sGenericdelegate = null;
Genericdelegate<DateTime> dtGenericdelegate = null;

PS:
1.占位符可用于当做返回值,比如:
①.接口中:

public interface GenericInterfac<T>
{
    //T当做返回值
    public T Show();
}

②.类中:

public class GenericClass<T>
{
    public T Show()
	{
        return default(T);
	}

    public T Show2(T t)
	{
        return t;
	}
}

2.如果需要当做父类去继承,继承时必须指定类型

/// <summary>
/// 父类
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class GenericAbstractClass<T>
{
}

①.普通类:

public class ChildClass : GenericAbstractClass<int>
{
}

②.子类同样是泛型的话,可使用子类的占位符

public class ChildClass<S> : GenericAbstractClass<S>
{
}

3.实现泛型接口

/// <summary>
/// 实现的接口
/// </summary>
/// <typeparam name="T"></typeparam>
public interface GenericInterfac<T>
{
}

①.普通类实现

public class ChildClass : GenericInterfac<int>
{
	public int Show()
	{
		throw new NotImplementedException();
	}
}

②.泛型类实现

public class ChildClass<S> : GenericInterfac<S>
{
	public S Show()
	{
		throw new NotImplementedException();
	}
}

五、泛型约束

类:

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

public class People2
{
	//带参构造函数
	public People2(int id)
	{
	}
	public int Id { get; set; }
    public string Name { get; set; }
}

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

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

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

接口:

public interface ISports
{
    void Pingpang();
}

1、基类约束

①.where后面指定类为参数类型
②.可以传递指定类与其子类
③.因为约束的存在,只要能成功传入参数,就一定没问题,所以不会存在类型安全问题。

展示用的方法:

//把类型参数当做People
public static void Show<T>(T tParameter) where T:People
{
	Console.WriteLine($"People.Id = {tParameter.Id}")
}

调用:

People people = new People()
{
	Id = 123,
	Name = "张三"
};
Show<People>(people);

Chinese chinese = new Chinese()
{
	Id = 123,
	Name = "李四"
};
Show<Chinese>(chinses);

2、接口约束

①.where后面指定接口为参数类型
②.只能传递指定接口或者是实现过这个接口的类
③.接口约束可以获取、增加接口中的功能

展示用的方法:

public static void ShowInterface<T>(T tParameter) where T:ISports
{
	//调用接口中的功能
	tParameter.Pingpang();
}

调用:

Chinese chinese = new Chinese()
{
	Id = 123,
	Name = "李四"
};
ShowInterface<Chinese>(chinses);

3、引用类型约束

①.就只能传递引用类型,比如where后指定class就只能传递类

展示用的方法:

public static void ShowClass<T>(T tParameter) where T:class
{
}

调用:

People people = new People()
{
	Id = 123,
	Name = "张三"
};
ShowClass<People>(people);

Chinese chinese = new Chinese()
{
	Id = 123,
	Name = "李四"
};
ShowClass<Chinese>(chinses);

4、值类型约束

①.就只能传递值类型,比如int、DateTime

展示用的方法:

public static void ShowStruct<T>(T tParameter) where T:struct
{
}

调用:

ShowStruct<int>(123);
ShowStruct<DateTime>(DateTime.Now);

5、无参数构造函数约束

----没有有参数构造函数约束

①.必须有一个无参数构造函数才能当做参数传入

展示用的方法:

public static void ShowNew<T>(T tParameter) where T:new()
{
	T t = new T();
}

调用:

People people = new People()
{
	Id = 123,
	Name = "张三"
};
ShowNew<People>(people);

//报错
People2 people2 = new People2()
{
	Id = 123,
	Name = "张三"
};
ShowNew<People2>(people2);

6、枚举约束

①.必须是枚举才能传入

展示用的方法:

public static void ShowEnum<T>(T tParameter) where T:Enum
{
	T t = new T();
}

调用:

public Enum UserType
{
	Chinese = 1,
	Japanese = 2
}
ShowEnum<Enum>(User.Chinese);

7、其他补充

①、要么是父子关系,要么是同一个类型

展示用的方法:

public static void ShowParent<T,S>(T tParameter , S sParameter) where T:S
{
}

调用:

People people = new People()
{
	Id = 123,
	Name = "张三"
};

Chinese chinese = new Chinese()
{
	Id = 123,
	Name = "李四"
};

//父子关系调用
//子类在前,父类在后
ShowParent<Chinese , People>(chinese , people);

//同类型调用
ShowParent<Chinese , Chinese>(chinese , chinese);

六、泛型缓存

----泛型缓存的本质是泛型类

泛型缓存:

//泛型缓存--可以根据不同的类型生成一个新的类的副本,几种类型就会生成几个副本;
for (int i = 0; i < 5; i++)
{
    Console.WriteLine(GenericCache<int>.GetCache()); //GenericCacheInt
    Thread.Sleep(10);
    Console.WriteLine(GenericCache<long>.GetCache());// GenericCachelong
    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);
}

/// <summary>
///泛型缓存:
/// </summary>
/// <typeparam name="T"></typeparam>
public class GenericCache<T>
{
	//每种类型都会执行一次静态方法,且执行一次
    static GenericCache()
    {
        Console.WriteLine("This is GenericCache 静态构造函数");
        //_TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff")); 
        _TypeTime = $"{typeof(T).FullName}_{DateTime.Now.ToString("yyyyMMddHHmmss.fff")}";
    }

    private static string _TypeTime = "";

    public static string GetCache()
    {
        return _TypeTime;
    }

普通的字典缓存(用于比较):

//字典缓存
{
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(DictionaryCache.GetCache<int>()); //GenericCacheInt
        Thread.Sleep(10);
        Console.WriteLine(DictionaryCache.GetCache<long>());// GenericCachelong
        Thread.Sleep(10);
        Console.WriteLine(DictionaryCache.GetCache<DateTime>());
        Thread.Sleep(10);
        Console.WriteLine(DictionaryCache.GetCache<string>());
        Thread.Sleep(10);
        Console.WriteLine(DictionaryCache.GetCache<GenericCacheTest>());
        Thread.Sleep(10);
    }
}

/// <summary>
/// 字典缓存:静态属性常驻内存
/// </summary>
public class DictionaryCache
{
    private static Dictionary<Type, string> _TypeTimeDictionary = null;

    //静态构造函数在整个进程中,执行且只执行一次;
    static DictionaryCache()
    {
        Console.WriteLine("This is DictionaryCache 静态构造函数");
        _TypeTimeDictionary = new Dictionary<Type, string>();
    }
    public static string GetCache<T>()
    {
        Type type = typeof(T);
        if (!_TypeTimeDictionary.ContainsKey(type))
        {
            _TypeTimeDictionary[type] = $"{typeof(T).FullName}_{DateTime.Now.ToString("yyyyMMddHHmmss.fff")}";
        }
        return _TypeTimeDictionary[type];
    }
}

七、泛型的协变、逆变

----协变逆变只针对泛型接口和泛型委托

类:

/// <summary>
/// 动物
/// </summary>
public class Animal
{
    public int Id { get; set; }
}

/// <summary>
/// Cat 猫
/// </summary>
public class Cat : Animal
{
    public string Name { get; set; }
}

1.泛型存在不友好、不协调的地方(list本身就是一个泛型集合)。

//任何子类都可以使用父类来声明
//一只猫可以是一只动物
Animal animal2 = null;
animal2 = new Cat();

//下面的代码会报错:
//一群猫没办法成为一群动物
List<Animal> animalList2 = null;
// //不能在左边用父类
animalList2 = new List<Cat>();

在这里插入图片描述
普通的解决办法:

List<Animal> animalList3 = new List<Cat>().Select(c => (Animal)c).ToList();

2.协变
官方文档中的IEnumerable:

public interface IEnumerable<out T> : IEnumerable
{
    new IEnumerator<T> GetEnumerator();
}

IEnumerable的使用:

//IEnumerable 也经常把他当成一个集合来用
//协变:让右边用子类,能让左边用父类
IEnumerable<Animal> animalList1 = new List<Animal>();
IEnumerable<Animal> animalList2 = new List<Cat>();

OUT:

/// <summary>
/// out 协变 只能是返回结果 ,还是int 也是一种高级约束,避免出现问题
/// 泛型T 就只能做返回值; 不能做参数; 
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ICustomerListOut<out T>
{
    T Get();

    //void Show(T t);
}

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

//out:修饰类型参数;就可以让右边用子类,能让左边用父类
ICustomerListOut<Animal> customerList = new CustomerListOut<Cat>(); 

3.逆变
IN:

/// <summary>
/// T 就只能做参数  不能做返回值
/// </summary>
/// <typeparam name="T"></typeparam>
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)
    {

    }
}

//逆变:就可以让右边用父类;左边用子类
//逆变: In 类型参数只能做参数 ,不能做返回值
ICustomerListIn<Cat> customerList = new CustomerListIn<Animal>();

4.协变逆变同时存在

public interface IMyList<in inT, out outT>
{
    void Show(inT t);
    outT Get();
    outT Do(inT t);
}

//协变逆变的存在,就是为了满足常规场景添加一个避开风险的约束; 
{
    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>();//协变+逆变
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

焦糖丨玛奇朵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值