C#泛型(详解)

news2024/11/24 6:36:43

前言 

介绍C# 入门经典第8版书中的第12章《泛型》   

一、泛型的含义

为引出泛型的概念,我们先来看看我们前面提到的 集合类icon-default.png?t=N7T8https://blog.csdn.net/qq_71897293/article/details/134684612?spm=1001.2014.3001.5501

这些集合是没有具体类型化的,所以我通常都是把object项转换为集合中实际存储的对象类型。object类型代表所有数据类型都可以储存,但有些时候存储的一些数据类型可能会导致我们程序抛出异常,但我们前面在集合类中都提到了,解决方法。但更好的解决方法,就是创建强类型化的集合类。这种类型的集合派生于CollectionBase。但会有几个问题:

  • 每次创建时需要定义集合类包含的新类型的项
  • 创建后还需要为其提供需要实现的方法

一方面新的类型需要额外的功能,所有每次我们创建集合类时,都会花费大量的时间。另一方面,泛型类大大简化了这个问题,可以快速创建强类型集合,创建“T”类型对象集合,只需要一行代码。

        Collection<Program> collection = new Collection<Program>();

二、使用泛型

1、可空类型

        我们在前面学习过程中,也了解到了很多基本类型,如int、String 其中值类型是不能为null,int就是值类型,所以他的默认值就是0,但我们有时在某些应用场景中需要将int 变为可空类型。可以使用如下代码创建

 System.Nullable<int> @int=null;

注意:创建类型变量时,无论是初始化为null还是通过赋值来初始化,都必须在使用前初始化。

上面的创建代码等同于

        System.Nullable<int> @int =new System.Nullable<int>();

        我们将 @int 变量设置为可空类型,那么它与原来的 int 类型不同的是,它可以为null其他的功能和int 类型一致 。既然可以为null,那我们可以对 @int 进行可空类型判断

class Program
{
    static void Main(string[] args)
    {
        System.Nullable<int> @int = new System.Nullable<int>();

        if (@int != null)
        {

        }


        if (@int.HasValue)
        {

        }
        //两种方法一致效果
    }

}

注意:

HasValue属性,在我们上面这种判断方式是不可行的。因为引用类型没有初始化,值本身就是null,引用类型的null表示对象不存在,不存在我们调用属性就自然会报错。


属性 Value 如果 HasValue 为true 则代表 Value 有值,反则 HasValue 为false则说明变量被赋值了null,访问value属性就会抛出异常

我们经常在代码中定义可空类型,以至于C#帮我们简化了一种写法 表示相同意思

int? @int;

1.1 运算符和可空类型 

        在简单的类型中,如 int,创建一个int类型的可空类型,这个可空类型和它原来的数据类型没有太大的区别如。+\-\*\/ 使用运算符来计算。

int? @int = 5; int? @int2 = 5;
int? @int3 = @int + int2;

但如果使用以下代码就会无法通过编译。

int @int4 = @int + int3;

只能通过显示转换,或者通过Value属性进行运算。如下示例:

 //无法编译 如果想通过编译 那么只能显示转换
 int @int4 = (int)(@int + int3);


 //写法2 通过前面用到的 Value属性 在Nullable<int> 结构当中提供  其中 int?和Nullable<int>效果是一致的 其中int?只不过是他的简写罢了。

 int @int5 = @int.Value + @int3.Value;

 //注意这里的Value 有值的前提我们也提到了 是在.HasValue属性为true时才有值,否则为false 

介绍完上面的代码我们在来看看下面这段代码:大家觉得这段运行结果是什么?

int? inta = 5;
int? intb = null;
int? intc = inta * intb;

         对于除bool?外的所有简单可空类型,在值为null的时候,我们统称为不可计算那么问题来了 为什么不能除bool外呢?  因为bool类型的比较通常 就是 && || & | 这四种比较方式,在其中比较的一边为null时 另一边就是整个表达式结果的值。
简要就是 不需要其中一个操作符的值即可计算出结果,则操作数是否为null就不重要。

1.2 ??运算符

        当前运算符的效果,可以进一步缩小我们处理可空类型所需的代码量(引用类型也是可空类型)?? 运算符为空接合运算符  是一个二元运算符 允许给可能等于null的表达式提供另一个值。使用方式:

string Studentzhang = null;
string Studentlong = "龙";
string str = Studentzhang ?? Studentlong;
string str2 = Studentzhang == null ? Studentlong : Studentzhang;

        如果第一个的操作符不是null 那么当前运算符的结果就是第一个操作数,否则,就是第二个操作数。上面的两个表达式的作用是相同的。

        上面的Studentzhang为null时 则str的值就是Studentlong,和下面的三元运算符效果一致。

1.3 ?.运算符

        这个操作符通常被称为Elvis运算符或空条件运算符。实际效果,可以在结果是null时返回一个默认值。如下代码:

 if (Student.student.CollectionBase != null)
 {
     int count = Student.student.CollectionBase.Count;
 }

类代码:

 class Student
 {
     public static Student student;
     static Student() { student = student ?? new Student(); }
     public ArrayList CollectionBase { get; set; }
 }

上述的代码,对属性进行了判断是否null,如果不为null时,则将数组的数量赋值给变量。但如果您不对属性进行判断,则会抛出异常。下面代码无法执行

 int count1 = Student.student.CollectionBase.Count;

这时我们可以使用空条件运算符。

int? count2=Student.student.CollectionBase?.Count;

        我们在看一段代码,下述代码结合了我们上面讨论到的空合并运算符和现在讲到的空条件运算符以及一个int类型的可空类型。

 int? counts = Student.student.CollectionBase?.Count ?? 0;

 2、System.Collections.Geneir名称空间

在当前名称空间下,我们要介绍其中的两个类型。

类型说明
List<T>                                          T类型对象的集合
Dictionary<K,V>                            与K类型的键值相关的V类型的项的集合

拿其中的List<T>泛型集合来说,我们创建它的实例,就可以使用类。也可以使用我们之前创建集合中,所实现的方法如Add Remove 等,可以大幅度的缩少代码,而且创建十分简单。

System.Collections.Generic.List<T> s = new System.Collections.Generic.List<T>();

注意:这里的T是需要您指定具体类型。

2.1 List<T>使用

下面我将对List集合的使用,提供一个示例:

using System.Collections.Generic;
using static System.Console;

namespace Ch12Ex02
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Animal> animalCollection = new List<Animal>
            {
                new Dog("张得帅"),
                new Chicken("张得酷")
            };
            foreach (Animal myAnimal in animalCollection)
            {
                myAnimal.Feed();
            }
            ReadKey();
        }
    }
    public abstract class Animal 
    {
        protected string name;
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public Animal()
        {
            name = "没有名字";
        }

        public Animal(string newName)
        {
            name = newName;
        }

        public void Feed() => WriteLine($"{name} 在吃饭");
    }

    public class Chicken : Animal
    {
        public void LayEgg() => WriteLine($"{name} 下了一个蛋");

        public Chicken(string newName) : base(newName) { }
    }

    public class Dog : Animal
    {
        public void Crap() => WriteLine($"{name} 拉了一坨屎");

        public Dog(string newName) : base(newName) { }
    }
}

        其中特别的是  List<Animal> animalCollection = new List<Animal> 我们只用一行就能创建一个集合,把当前集合指定为Animal的类型集合。获取相同效果,我们可以改一下Animal的定义,将它继承List<Animal>也就是说Animal类本身可以被视为一个Animal对象的列表。 可以更容易看懂,还可以在适当的时候给Animal添加成员。如下所示:

   public abstract class Animal : List<Animal>
    {
        protected string name;
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public Animal()
        {
            name = "没有名字";
        }

        public Animal(string newName)
        {
            name = newName;
        }

        public void Feed() => WriteLine($"{name} 在吃饭");
    }

2.2 对泛型列表进行排序和搜索

这一段书中是详细介绍一段示例代码,如下代码是书中的示例代码。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
using static System.Math;
namespace Ch12Ex03
{
    class Program
    {
        static void Main(string[] args)
        {
            Vectors route = new Vectors
            {
                new Vector(2.0, 90.0),
                new Vector(1.0, 180.0),
                new Vector(0.5, 45.0),
                new Vector(
                    2.5, 315.0)
            };
            WriteLine(route.Sum());
            route.Sort(VectorDelegates.Compare);
            WriteLine(route.Sum());
            Predicate<Vector> searcher = new Predicate<Vector>(VectorDelegates.TopRightQuadrant);
            Vectors topRightQuadrantRoute = new Vectors(route.FindAll(searcher));
            WriteLine(topRightQuadrantRoute.Sum());
            ReadKey();
        }
    }
    public class Vectors : List<Vector>
    {
        public Vectors()
        {
        }
        public Vectors(IEnumerable<Vector> initialItems)
        {
            foreach (Vector vector in initialItems)
            {
                Add(vector);
            }
        }
        public string Sum()
        {
            StringBuilder sb = new StringBuilder();
            Vector currentPoint = new Vector(0.0, 0.0);
            sb.Append("origin");
            foreach (Vector vector in this)
            {
                sb.AppendFormat($" + {vector}");
                currentPoint += vector;
            }
            sb.AppendFormat($" = {currentPoint}");
            return sb.ToString();
        }
    }
    public static class VectorDelegates
    {
        public static int Compare(Vector x, Vector y)
        {
            if (x.R > y.R)
            {
                return 1;
            }
            else if (x.R < y.R)
            {
                return -1;
            }
            return 0;
        }
        public static bool TopRightQuadrant(Vector target)
        {
            if (target.Theta >= 0.0 && target.Theta <= 90.0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }
    public class Vector
    {
        public double? R = null;
        public double? Theta = null;
        public double? ThetaRadians
        {
            // Convert degrees to radians.
            get { return (Theta * Math.PI / 180.0); }
        }
        public Vector(double? r, double? theta)
        {
            // Normalize.
            if (r < 0)
            {
                r = -r;
                theta += 180;
            }
            theta = theta % 360;
            // Assign fields.
            R = r;
            Theta = theta;
        }
        public static Vector operator +(Vector op1, Vector op2)
        {
            try
            {
                // Get (x, y) coordinates for new vector.
                double newX = op1.R.Value * Sin(op1.ThetaRadians.Value)
                   + op2.R.Value * Sin(op2.ThetaRadians.Value);
                double newY = op1.R.Value * Cos(op1.ThetaRadians.Value)
                   + op2.R.Value * Cos(op2.ThetaRadians.Value);
                // Convert to (r, theta).
                double newR = Sqrt(newX * newX + newY * newY);
                double newTheta = Atan2(newX, newY) * 180.0 / PI;
                // Return result.
                return new Vector(newR, newTheta);
            }
            catch
            {
                // Return "null" vector.
                return new Vector(null, null);
            }
        }
        public static Vector operator -(Vector op1) => new Vector(-op1.R, op1.Theta);
        public static Vector operator -(Vector op1, Vector op2) => op1 + (-op2);
        public override string ToString()
        {
            // Get string representation of coordinates.
            string rString = R.HasValue ? R.ToString() : "null";
            string thetaString = Theta.HasValue ? Theta.ToString() : "null";
            // Return (r, theta) string.
            return string.Format($"({rString}, {thetaString})");
        }
    }
}

为了便于理解这是我理解后写的代码:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Documents;
using static System.Console;
class Program
{
    static void Main(string[] args)
    {
        Student student = new Student()
        {
            new StudentAge (10),
            new StudentAge (18),
            new StudentAge (10),
            new StudentAge (58),
            new StudentAge (15)
        };
        student.StudentWrile();

        //排序
        student.Sort(PRP.MYCompare);
        student.StudentWrile();

        Student studentA = new Student(student.FindAll(PRP.Tiaojianpanduan));

        studentA.StudentWrile();

        ReadKey();
    }
}
class Student : List<StudentAge>
{
    object obj = new object();

    public void StudentWrile()
    {
        foreach (var item in this)
        {
            WriteLine(item.age);
        }
        Console.WriteLine(DateTime.Now.ToString("T"));
    }
    public Student()
    {

    }
    public Student(IEnumerable<StudentAge> objects)
    {
        AddRange(objects);
    }
    public void Chaxun()
    {

    }



}
class StudentAge
{
    public int age { get; set; }
    public StudentAge(int age)
    {
        this.age = age;
    }
    public static StudentAge GetStudentAge(object a)
    {
        if (a is StudentAge)
        {
            return a as StudentAge;
        }
        else
        {
            throw new Exception("");
        }
    }
}
static class PRP
{
    public static int MYCompare(object x, object y) => ((StudentAge)x).age - ((StudentAge)y).age;

    public static bool Tiaojianpanduan(StudentAge studentAge)
    {

        if (studentAge?.age > 10)
        {
            return true;
        }
        return false;
    }
}

示例解释:

第一点:上述代码将Student 类继承自 List<StudentAge>,这样可以直接使用列表类提供的方法,并且可以在其基础上扩展自己的功能。提高了代码可读性。这对我们日后的开发可以提高我们的代码阅读性,还可以扩展。

第二点:还用到了FillAll方法 ,方法参数 委托参数。需要是具有int类型的返回值 和两个泛型参数的函数。

第三点:集合中的Sort方法这是我们第三种用法了,前面文章已经结束了两种,一种是无参,一种是传输实现了IComparer接口的实例。

2.3 Dictionary<K,V>

        当前集合的K,与Y为两个类型。其中我将介绍泛型的字典集合怎么使用。即Dictionary集合。实现和上一次我们自定义字典集合的功能。

创建方式(三种)

  //创建方式
  Dictionary<string, int> studentDictionary = new Dictionary<string, int>();
  //添加方式
  studentDictionary.Add("张一", 20);
  studentDictionary.Add("张二", 50);
  studentDictionary.Add("张三", 80);

  //第二种添加方式
  Dictionary<string, int> keyValuePairs = new Dictionary<string, int>()
  {
      //添加方式
      { "张一", 20 },
      { "张二", 50 },
      { "张三", 80 }
  };

  //第三种 通过使用索引初始化器 它允许在对象初始化器内部初始化索引
  Dictionary<string, int> keyValuePairs3 = new Dictionary<string, int>()
  {
      //添加方式
      ["张一"] = 20,
      ["张二"] = 50,
      ["张三"] = 80
  };

示例解释:

当前添加数据,指定了键为string类型,值为int类型 。

第一种的方式:为我们常用的使用Add函数来添加。

第二种方式:不使用Add来添加,这里也没有太多介绍,懂得使用就可。

第三种方式:通过使用索引初始化器 它允许在对象初始化器内部初始化索引。

我们可以使代码更加简洁可以:

 //创建一个集合 并初始化
 var keyValuePairs3 = new Dictionary<string, int>()
 {
     //指定什么键 值为什么
     ["张一"] = 20,
     ["张二"] = 50,
     ["张三"] = 80
 };
 //这里使用方法来创建
 var keyValuePairs4 = GetDictionary();
 static Dictionary<string, int> GetDictionary() => new Dictionary<string, int>() 
 { ["张一"] = 20, ["张二"] = 50, ["张三"] = 80 };

迭代方式:

   //迭代集合中的建集合 
   foreach (var item in keyValuePairs3.Keys)
   {
       WriteLine("集合中的键:" + item);
   }
   WriteLine("------------------");
   //迭代集合中的值集合
   foreach (var item in keyValuePairs3.Values)
   {
       WriteLine("集合中的值:" + item);
   }
   WriteLine("------------------");
   //迭代集合本身
   foreach (KeyValuePair<string, int> item in keyValuePairs3)
   {
       WriteLine("键:" + item.Key + " 值:" + item.Value);
   }
   WriteLine("------------------");
   ReadKey();

提示:1 KeyValuePair 和我们之前创建集合类中的 DictionaryEntry 很相似。2 集合中的键是唯一的,一旦重复添加则会引出异常。3 我引用了 using static System.Console; 为命名空间

指定 Dictionary<K,V> 比较键的自定义比较器

集合的键的判断方式为忽略大小写,所以当你添加一个为大写一个为小写时会报错 

 Dictionary<string, int> keyValuePairs = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
 {
     ["tow"] = 15,
     ["Tow"] = 15
     //这里使用索引初始化器 似乎不会引起异常 我还不知道原因。
 };
 keyValuePairs.Add("sum", 20);
 keyValuePairs.Add("Sum", 20);
 ReadKey();

运行预览: 

907cc5bf0c42427c9dfb7f3444953094.png

三、定义泛型类型

1、定义泛型类

我们在类中定义泛型类只需要像如下代码定义就可以定义一个泛型类

/// <summary>
/// 定义泛型类
/// </summary>
/// <typeparam name="T"> T 泛型参数的标识符  但这不一定是用T表示 也可以使用别的代表 只需要符合C#命名规则就可以。也可以定义多个类型参数 </typeparam>
public class Student<T, T2, T3>
{
    //定义的类型参数 T 
}
public class Teacher<T, T2, T3>
{
    //定义的类型参数 T  T2  T3
}

示例解释:

类型“T”是一个泛型参数的标识符。“T”本身并不代表某个特定的类型,而是一个占位符,当你实际使用到这个类时,你可以为“T”提供具体 的一个类类型。

        定义泛型类成员,我们可以把刚刚定义的类型 定义为我们的属性、变量的类型,方法等成员的返回值。或者方法的参数

/// <summary>
/// 定义泛型类
/// </summary>
/// <typeparam name="T"> T 泛型参数的标识符   </typeparam>
public class Student<T>
{
    //定义T 类型成员
    private T _first;
    //定义T 类型属性
    public T First { get; set; }
    private T _second;
    //定义返回T类型的方法
    public T GetSecond() => _second;
    public Student(T first)
    {
        //定义T类型的变量
        T firsts = first;
        _first = firsts;
    }
}

        注意我们不能假定“T” 为某种类型,如下代码是不可行的。(我们将上一个“Student”类的构造函数修改一下)

  public Student(T first)
  {
      // 这里尝试使用 new T() 可能导致编译错误,因为编译器无法保证 T 是引用类型或有默认的无参构造函数。
      // 泛型类型 T 的行为和特性不确定,因此这种构造方法可能会在某些情况下失败。
      first = new T();
  }

常见的泛型中的方法:获取泛型参数的标识符类型

using static System.Console;
public class Program
{
    static void Main()
    {
        Student<string, int, double> student = new Student<string, int, double>();
        WriteLine(student.GetAllTypeAsString());
        ReadKey();
    }
}

public class Student<T, T2, T3>
{
    public string GetAllTypeAsString()
    {
        return "T1:" + typeof(T) + "\nT2:" + typeof(T2) + "\nT3:" + typeof(T3);
    }
}

注意:我们在比较,泛型类提供的类型值和null 时,只能使用运算符== 或 != ,如下代码

public bool Compare( T t1,T2 t2)
{
    if (t1 != null && t2 != null)
    {
        return true;
    }
    return false;
}

示例解释:

在方法 Compare 中,使用 t1 != null && t2 != null 这样的方式来比较两个泛型类型的值是否为 null,这样的比较并不适用于所有类型。泛型类型并不一定要求支持 null。例如,如果 TT2 是值类型(例如 intdouble 等),那么它们就不能赋值为 null。在这种情况下,你的比较会导致编译错误或者抛出异常。常见的就是一直返回true,因为值类型不会等于 Null,除非你是可空类型。

扩展


如果你想要更可靠地检查泛型类型是否为 null,可以考虑使用 EqualityComparer<T>.Default 类来进行比较 我修改一下比较代码

public class Student<T, T2>
{
    //比较
    public bool Compare(T t1, T2 t2)
    {
        // 检查 t1 和 t2 是否为 null
        bool t1IsNull = EqualityComparer<T>.Default.Equals(t1, default(T));
        bool t2IsNull = EqualityComparer<T2>.Default.Equals(t2, default(T2));

        return t1IsNull && t2IsNull;
    }
}

1.1 Default关键字

default关键字可以获取当前数据类型的默认数据。

举个例子:

public class Student<T, T2>
{
    public Student(T t1) => t1 = default(T); 
}

示例解释:

 t1 = null;//不能写等于 null 或者是0 因为无法假定t2为一种类型
  对于引用类型 T,t1 = null 是合法的,表示空值。
  对于值类型 T,t1 = default(T) 可以获得其默认值,例如数值类型为 0,布尔类型为 false。 

1.2 约束类型

        泛型可以对我们之前在泛型类中定义的泛型参数的标识符进行一个约束。泛型参数的标识符可以简称为类型参数。使用关键字 Where来实现约束类型参数。

public class Student<T, T2> where T : new()
{
}
public class Student2<T, T2> where T : Constraint, new()  
{
}
public class Student3<T, T2> where T : class where T2 : struct
{
}
public class Student4<T, T2> : CollectionBase where T : class where T2 : struct
{
}
public class Student5<T, T2>  where T : class where T2 : T
{
}
public class Student6<T, T2>  where T : T2 where T2 : T
{
}

示例解释: 

  1. 使用关键字 Where来实现
  2. 多个约束用逗号来区分开
  3. 可以使用多个where语句
  4. 约束必须出现在继承说明符的后面
  5. 可以把其它的类型参数的约束用作自身的类型约束
  6. 类型参数的约束不能循环 (”Student6“类 编译器会报错)

接下来我提供一个约束的表格,表达各种约束的含义

约束定义注意
Struct类型必须是值类型
Class类型必须是引用类型
interface类型必须是接口,或实现了接口提供某接口
new()类型必须有一个公共的无参构造函数必须是类型约束的最后一个约束
base-Class类型必须是某类的基类,或者继承自基类提供某类名

1.3 从泛型类中继承

1 约束在继承中对未约束的类型进行约束:如果一个基类使用了某种约束,比如 where T : class,那么派生类可以进一步添加额外的约束条件,对未被约束的类型参数进行约束。

举个例子:

class Base<T> where T : class { }

class Derived<T> : Base<T> where T : IEnumerable { }

示例解释:

在这个例子中,Base<T> 有一个约束 where T : class,而 Derived<T> 继承自 Base<T> 并添加了一个额外的约束 where T : IEnumerable,这样就对 T 进行了进一步的约束。 

2 基类约束的继承性:如果基类对某个类型参数施加了约束,那么派生类中对于相同类型参数的约束至少要与基类的约束相同或更严格。

举个例子:

class Base<T> where T : class { }

// 这是合法的,因为派生类的约束至少与基类相同
class Derived<T> : Base<T> where T : class { }

// 这是不合法的,因为派生类的约束比基类宽松了
class Derived<T> : Base<T> where T : struct { }

3 提供类型的要求:如果继承了一个泛型类型,派生类必须提供类型参数。

举个例子:

class Base<T> { }

// 这是不合法的,因为没有提供类型参数
class Derived : Base { }

// 这是合法的,因为提供了类型参数
class Derived<T> : Base<T> { }

        这些规则帮助在派生类中维持和扩展对泛型类型参数的约束,并确保继承关系中的类型参数满足基类约束。 

提示:

        如果给泛型类型提供了参数,我们称呼该类型为“关闭的”,相反则是“打开的” 如lsit<Student> list<T>

1.4 泛型运算符

        C# 中的泛型运算符允许你对泛型类型参数进行某些操作,比如执行算术操作、逻辑比较等。这些运算符可以使用在泛型类、泛型结构体、以及泛型方法中。

public class MathOperations<T>
{
    public T Add(T a, T b)
    {
        return (dynamic)a + (dynamic)b;
    }

    public T Subtract(T a, T b)
    {
        return (dynamic)a - (dynamic)b;
    }

    // 其他算术运算符方法...
}

示例解释:

        在这个示例中,MathOperations<T> 类演示了使用泛型类型参数 T 进行加法和减法运算。通过使用 dynamic 关键字,可以允许在编译时确定运算符的实际操作。 

public class ComparisonOperations<T>
{
    public bool AreEqual(T a, T b)
    {
        return EqualityComparer<T>.Default.Equals(a, b);
    }

    // 其他逻辑运算符方法...
}

示例解释:

        这个示例展示了如何使用 EqualityComparer<T> 类进行比较运算。这个类允许你比较泛型类型的值,检查它们是否相等。 

public class GenericOperator<T>
{
    public static T operator +(GenericOperator<T> a, GenericOperator<T> b)
    {
        // 实现泛型类型的加法运算符重载
        // ...
    }

    // 其他运算符重载方法...
}

示例解释:

        这个示例展示了如何在泛型类中重载运算符。通过在泛型类中定义运算符的重载,可以让泛型类型支持各种自定义的操作。 

 1.5 泛型结构

泛型结构的定义与泛型类的定义相同。

using static System.Console;
public class Program
{
    static void Main()
    {
        MathStruct<double, int> mathStruct = new MathStruct<double, int>(100);
        mathStruct.MyProperty = 1;
        WriteLine(mathStruct.GetSum());
        ReadKey();
    }
}
public struct MathStruct<T23, T24> where T23 : struct where T24 : struct
{
    private T23 myVar;
    public T23 MyProperty
    {
        get { return myVar; }
        set { myVar = value; }
    }
    private T24 sum;
    //定义返回类型参数值的方法
    public T24 GetSum() => sum;
    public MathStruct(T24 t24)
    {
        sum = t24;
        myVar = default;
    }
}

输出结果:100 

2、定义泛型接口

public interface IMyinterface<T>
{
    T GetValue();
    T MyProperty { get; set; }
}

3、定义泛型方法

泛型方法使得你能够在方法内部使用不特定类型的数据,提高了代码的重用性和灵活性。

 public static T convert<T>(object s) where T : struct
 {
     if (s is T)
     {
         return (T)s;
     }
     return default;
 }

示例解释:

   if (s is T) 尝试检查对象 s 是否可以转换为类型 T。如果可以,就执行强制类型转换 (T)s 并返回转换后的值。否则,将返回 default(T),即 T 类型的默认值。 

4、定义泛型委托

举个例子:

public delegate void Mydelegate<T, T2>(T t, T2 t2) where T : T2 where T2 : class;

 示例解释:

  创建了一个委托无返回类型,有两个类型参数。分别为"T" "T2",对"T"的类型参数的约束是与"T2"的约束一致,"T2" 的约束则为引用类型

四、变体

1、协变

        协变,对于接口定义,协变类型参数只能用作方法的返回值或属性的Get访问器。使用关键字out。个人理解:可以将子类对象赋值给父类对象。

举个列子:

using System;

namespace ConsoleApp2
{
    internal class Program
    {
        static void Main(string[] args)
        {
            IMyInterface<BaseClass> interfaceInstance = new Student<DerivedClass>();
            BaseClass baseInstance = interfaceInstance.GetBase();
        }

    }

    interface IMyInterface<out T>
    {
        T GetBase();
    }

    class BaseClass
    {

    }

    class DerivedClass : BaseClass
    {

    }

    class Student<T> : IMyInterface<T>
    {
        public T GetBase()
        {
            return default;
        }
    }
}

示例解释:

         在 Main 方法中,我们创建了一个 Student<DerivedClass> 类的实例,并将其赋值给了 IMyInterface<BaseClass> 类型的变量 interfaceInstance。这里可以这么做是因为 Student<DerivedClass> 类型实现了 IMyInterface<DerivedClass> 接口,而由于协变的作用,IMyInterface<DerivedClass>IMyInterface<BaseClass> 的子类型,所以可以将 Student<DerivedClass> 类型赋值给 IMyInterface<BaseClass> 类型的变量。

总的来说,这段代码通过使用协变实现了将一个派生类型 Student<DerivedClass> 赋值给一个基类类型 IMyInterface<BaseClass> 的变量,允许在接口中使用更具体的类型。

  

2、抗变

        协变,对于接口定义,协变类型参数只能用作方法的参数,不能用作返回值。使用关键字in。个人理解:可以将父类对象赋值给子类对象。

举个例子:

using System;

namespace ConsoleApp2
{
    internal class Program
    {
        static void Main(string[] args)
        {
            IMyInterface<DerivedClass> interfaceInstance = new Student<BaseClass>();
            interfaceInstance.GetBase(new DerivedClass());
        }

    }

    interface IMyInterface<in T>
    {
        void GetBase(T s);
    }

    class BaseClass
    {

    }

    class DerivedClass : BaseClass
    {

    }

    class Student<T> : IMyInterface<T>
    {
        public void GetBase(T s)
        {
            // 在实际场景中可能有不同的实现
        }
    }
}

示例解释:

        在 Main 方法中,创建了一个 Student<BaseClass> 类的实例,并将其赋值给了 IMyInterface<DerivedClass> 类型的变量 interfaceInstance。这里可以这么做是因为 Student<BaseClass> 类型实现了 IMyInterface<BaseClass> 接口,由于逆变的作用,IMyInterface<BaseClass>IMyInterface<DerivedClass> 的父类型,所以可以将 Student<BaseClass> 类型赋值给 IMyInterface<DerivedClass> 类型的变量。

然后,调用了 GetBase 方法并传入了 DerivedClass 类型的参数,这是因为 IMyInterface<DerivedClass> 接口的实现需要一个 DerivedClass 类型的参数。

实际使用中,可以看一下下面这段代码:

using System.Collections.Generic;
using System;

class Program
{
    static void Main(string[] args)
    {
        B bInstance = new B("lsonsgsstsaos");

        // 协变
        IContravariant<B> contravariantB = bInstance;
        IContravariant<A> contravariantA = contravariantB;

        // 逆变
        ICovariant<B> covariantB = bInstance;
        ICovariant<C> covariantC = covariantB;

        List<B> list = new List<B>()
        {
            { new B("She") },
            { new C("Student") },
            { new B("from") }
        };

        list.Sort(OrderByStringLength);

        LoopThrough(list);

        Console.ReadKey();
    }

    private static int OrderByStringLength(B x, B y) => x.Name.Length.CompareTo(y.Name.Length);

    public static void LoopThrough(IEnumerable<A> enumerable)
    {
        foreach (var item in enumerable)
        {
            Console.WriteLine(item.Name);
        }
    }
}

public class Meto
{

}

public abstract class A
{
    public abstract string Name { get; set; }
}

class B : A, IContravariant<B>, ICovariant<B>
{
    public B(string name)
    {
        this.Name = name;
    }

    public override string Name { get; set; }
}

class C : B
{
    public C(string name) : base(name)
    {

    }
}

interface IContravariant<out T>
{

}
interface ICovariant<in T>
{

}

示例解释:   

        这段代码涉及到了协变和逆变,同时展示了类的继承和接口的使用。

  1. 协变和逆变的使用

    • 协变:IContravariant<out T> 接口使用了协变关键字 out,并在 Main 方法中展示了 IContravariant<B> 类型可以赋值给 IContravariant<A> 类型的示例。
    • 逆变:ICovariant<in T> 接口使用了逆变关键字 in,在 Main 方法中展示了 ICovariant<B> 类型可以赋值给 ICovariant<C> 类型的示例。
  2. 类和接口的关系

    • A 是一个抽象类,定义了一个抽象属性 Name
    • B 类继承自 A 类,并实现了 IContravariant<B>ICovariant<B> 接口。
    • C 类继承自 B 类。
  3. 方法和排序操作

    • OrderByStringLength 方法根据字符串长度对 B 类的实例进行排序。
    • LoopThrough 方法接受 IEnumerable<A> 类型的参数,并对其进行枚举操作。

总体来说,这段代码演示了协变和逆变在接口中的使用,以及类和接口之间的继承关系。同时也展示了泛型列表的排序和对列表的枚举操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1301557.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电脑待机怎么设置?让你的电脑更加节能

在日常使用电脑的过程中&#xff0c;合理设置待机模式是一项省电且环保的好习惯。然而&#xff0c;许多用户对于如何设置电脑待机感到困扰。那么电脑待机怎么设置呢&#xff1f;本文将深入探讨三种常用的电脑待机设置方法&#xff0c;通过详细的步骤&#xff0c;帮助用户更好地…

想进阶JAVA高级程序员吗?多线程必学

❤️作者主页&#xff1a;小虚竹 ❤️作者简介&#xff1a;大家好,我是小虚竹。2022年度博客之星评选TOP 10&#x1f3c6;&#xff0c;Java领域优质创作者&#x1f3c6;&#xff0c;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;掘金年度人气作…

openGauss学习笔记-151 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_basebackup

文章目录 openGauss学习笔记-151 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_basebackup151.1 背景信息151.2 前提条件151.3 语法151.4 示例151.5 从备份文件恢复数据 openGauss学习笔记-151 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_basebackup 151.1 …

使用xshell连接虚拟机(服务器)

作者&#xff1a;余小小 Xshell Xshell [1] 是一个强大的安全终端模拟软件&#xff0c;它支持SSH1, SSH2, 以及Microsoft Windows 平台的TELNET 协议。Xshell 通过互联网到远程主机的安全连接以及它创新性的设计和特色帮助用户在复杂的网络环境中享受他们的工作。 Xshell可以…

【XR806开发板试用】编译FreeRTOS系统

编译FreeRTOS系统&#xff0c;测试串口输出。 一、下载源码 1.1、获取源码 下载源码: rootubuntu:/home# wget https://bbs.aw-ol.com/assets/uploads/files/1693988430633-xr806_sdk.tar.gz 解压文件 rootubuntu:/home# tar xvf 1693988430633-xr806_sdk.tar.gz 1.2、获取…

亚马逊鲲鹏系统自动化操作更节约时间

亚马逊鲲鹏系统的自动化操作为卖家们带来了极大的时间节约和高效运营的利好。这一先进系统在账号注册、养号和下单等方面拥有强大的自动化功能&#xff0c;为卖家提供了简单便捷的操作体验。 首先&#xff0c;亚马逊鲲鹏系统在账号注册方面展现了其强大的自动化能力。用户只需事…

记录一次postgresql临时表丢失问题

项目相关技术栈 springboot hikari连接池pgbouncerpostgresql数据库 背景 为了优化一个任务执行的速度&#xff0c;我将任务的sql中部分语句抽出生成临时表&#xff08;create temp table tempqw as xxxxxxxxx&#xff09;&#xff0c;再和其他表关联&#xff0c;提高查询速…

小红书笔记投流全攻略,打造爆款内容

在小红书平台上&#xff0c;信息流投放和搜索广告是两种主要的广告形式。信息流投放主要通过用户刷作品时展示你的笔记&#xff0c;而搜索广告则是用户搜索相关关键词时展示出的内容。今天就和大家分享下小红书笔记投流全攻略&#xff0c;打造爆款内容&#xff01; 一、什么样你…

Python使用其它文件夹中的.py文件

一、背景 在python构建的工程中&#xff0c;A.py 可能要使用 B.py 文件中的函数、或者类、或者变量&#xff0c;如果这两个文件在同一个目录下&#xff0c;只需要在 A.py 中使用 import B 即可&#xff0c;但如果不在同一目录下&#xff0c;则这种方法不可用&#xff0c;将工程…

租房小程序源码开发中的5大注意事项

在租房小程序源码开发过程中&#xff0c;有许多关键的注意事项需要开发者和团队特别留意。作为该领域的专家&#xff0c;我将分享5大重要事项&#xff0c;以确保你的租房小程序源码开发顺利进行。 1. 确保代码可靠性和安全性 租房小程序源码的可靠性和安全性是开发过程中最重…

【C++】C++异常语法、使用、规范、异常安全及异常的优缺点

1. C异常概念 异常是一种处理错误的方式&#xff0c;当一个函数发现自己无法处理的错误时就可以抛出异常&#xff0c;让函数的直接或间接的调用者处理这个错误。 throw: 当问题出现时&#xff0c;程序会抛出一个异常。这是通过使用 throw 关键字来完成的。catch: 在您想要处理…

为什么说数字化转型能帮助企业降本增效?

引言 数字化转型是当今商业领域中的关键议题&#xff0c;它不仅是技术的应用&#xff0c;更是一种战略性的变革&#xff0c;对企业而言具有重要意义。在这个数字化时代&#xff0c;企业需要不断适应和采纳新技术&#xff0c;以获得竞争优势并提高效率。 数字化转型旨在将传统业…

成都工业学院Web技术基础(WEB)实验三:CSS字体等属性使用

写在前面 1、基于2022级计算机大类实验指导书 2、代码仅提供参考&#xff0c;前端变化比较大&#xff0c;按照要求&#xff0c;只能做到像&#xff0c;不能做到一模一样 3、图片和文字仅为示例&#xff0c;需要自行替换 4、如果代码不满足你的要求&#xff0c;请寻求其他的…

001两数之和

题意 给出一个数组和一个目标值&#xff0c;让你在数组中找出和为目标值的两个数&#xff0c;并且这两个数在数组中的下标&#xff08;索引&#xff09;不同。 示例 输入&#xff1a;nums[2,7,11,15],target9 输出&#xff1a;[0,1] 解释&#xff1a;因为nums[0]nums[1]9&#…

Draw.io or diagrams.net 使用方法

0 Preface/Foreword 在工作中&#xff0c;经常需要用到框图&#xff0c;流程图&#xff0c;时序图&#xff0c;等等&#xff0c;draw.io可以完成以上工作。 official website:draw.io 1 Usage 1.1 VS code插件 draw.io可以扩展到VS code工具中。

PHP基础 - 数组遍历与排序

介绍 在PHP中,数组遍历和排序是常见的操作,用于对数组中的元素进行访问和排序 数组遍历 1)数值数组的遍历 使用 foreach 循环遍历数组:foreach 循环是最常用的遍历数组的方法,它可以遍历索引数组和关联数组。例如:$fruits = array("apple", "banana&q…

浪涌保护器综合选型应用方案

浪涌保护器SPD是一种用于防止电气设备和电子信息系统受到雷电或其他过电压的影响的装置。浪涌保护器的选型是一个重要的工程问题&#xff0c;因为不同的应用场景和设备要求需要不同的浪涌保护器。本文将介绍浪涌保护器的行业分类选型方案&#xff0c;浪涌保护器选型的作用和意义…

智能优化算法应用:基于缎蓝园丁鸟算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于缎蓝园丁鸟算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于缎蓝园丁鸟算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.缎蓝园丁鸟算法4.实验参数设定5.算法…

Python Django Suit:构建现代化的Django后台管理

概要 Django Suit是一款为Django后台管理提供现代、优雅界面的第三方应用&#xff0c;它致力于提升Django开发者的管理体验。本文将深入介绍Django Suit的安装、配置和高级功能&#xff0c;提供详实的示例代码&#xff0c;帮助大家更好地使用和定制Django后台管理界面。 安装与…

科学小论文

赵州桥&#xff0c;是一座右拱桥&#xff0c;它座落于河北省石家庄市赵县城南液河之上。 赵州桥因赵县古称赵州而得名&#xff0c;当地人称之为大石桥&#xff0c;以区别于城西门外的永通桥&#xff0c;也称小石桥。 赵州桥始建于隋代&#xff0c;由匠师李春设计建造&#xff…