C#基础——泛型、泛型约束、泛型默认值

泛型

  • 正常定义数据的时候需要知道当前的类型,必须传入执行的类型,才可以正常编译
  • 而使用泛型则可以在编写代码时使用未确定的类型

泛型允许编写可以适用于多种数据类型的类、接口和方法。通过泛型,你可以编写更加灵活、可重用的代码。

定义泛型方法

public class MyClass {
	public void MyClassMethod<T>(T a) { //尖括号代表了泛型的数据类型,A是类型名称,a是参数
  		//只有在使用该方法的时候,才会根据赋值确定当前A的类型。
  		Console.WriteLine(a);
	}
}

 MyClass myClass = new MyClass();
//泛型可以代表任意数据类型
myClass.MyClassMethod(100);
myClass.MyClassMethod<string>("100");
myClass.MyClassMethod<bool>(false);
myClass.MyClassMethod2<string>("我是字符串类型");
myClass.MyClassMethod2<int>(123);

方法的返回值也可以使用泛型进行定义

public T MyClassMethod3<T>(T a, T b) {
  //如果a==b则返回a,否则返回b
  //因为在这里代码还不确定是什么类型, == 需要知道里面的值,泛型是先声明后赋值操作
  if (a.Equals(b)) {
    return a;
  } else {
    return b;
  }
}

//泛型的数据类型取决于开发者
Console.WriteLine(myClass.MyClassMethod3<int>(1, 2));
Console.WriteLine(myClass.MyClassMethod3<int>(2, 2));
Console.WriteLine(myClass.MyClassMethod3<string>("叽里呱啦a", "叽里呱啦b"));

泛型方法也可以传入多个泛型

//多个泛型 
public void MyClassMethod4<T, D>(T a, D d) {
   Console.WriteLine(a.GetType().ToString());
   Console.WriteLine(d.GetType().ToString());
 }

 //多个泛型
 myClass.MyClassMethod4<int, string>(100, "第二个泛型");
 //泛型也可以是对象当作泛型的类型
 People people = new People();
 people.Name = "我是对象";
 myClass.MyClassMethod4<People, string>(people, "第二个泛型");

泛型接口是一种可以定义参数化类型的接口,它可以用来描述对一组类型的操作,而不需要具体指定这些类型。

通过使用泛型接口,可以实现更灵活和通用的代码设计。

定义一个泛型接口

public interface InterfaceTest<T> {
  //test1 属性名
  T test1 { get; set; } //为将来继承自接口的类,提供了一个泛型属性
  string test2 { get; set; }
  void InterfaceTestMethod();
}

被泛型接口继承的类,在继承的时候需要明确接口的泛型类型

//如果是从泛型接口继承的,那么在继承的时候就需要告知当前接口的泛型类型
public class MyClass : InterfaceTest<int> {
  public int test1 { get; set; }
  public string test2 { get; set; }
  public void InterfaceTestMethod() {
    Console.WriteLine("该方法是从泛型继承过来的方法。");
  }
}

 MyClass myClass = new MyClass();
 myClass.test1 = 100;
 myClass.test2 = "小明";
 myClass.InterfaceTestMethod();
 Console.WriteLine($"{myClass.test2}考了:{myClass.test1}分");

被继承的泛型接口在使用的时候要明确泛型的类型

 public interface InterfaceTest2<D> : InterfaceTest<string> {
   D boom1 { get; set; }
   void InterfaceMethod2(D dd);
 }

//完成对 InterfaceTest2 的继承
public class MyClass2 : InterfaceTest2<int> {
  //如果类继承的接口还有其他的接口继承,那么是需要都实现的
  public int boom1 { get; set; }
  public void InterfaceMethod2(int dd) {
    Console.WriteLine("该方法是从泛型接口2继承过来的方法。", dd);
  }
  public string test1 { get; set; }
  public string test2 { get; set; }
  public void InterfaceTestMethod() {
    Console.WriteLine("该方法是从泛型接口1继承过来的方法。");
  }
}

MyClass2 myClass2 = new MyClass2();
myClass2.boom1 = 100;
myClass2.InterfaceMethod2(myClass2.boom1);

泛型约束

用于对泛型参数进行限制,以确保泛型参数满足特定的条件。泛型约束可以应用于泛型类、方法、接口和委托。

1、struct 约束:使用 struct 关键字可以确保泛型参数必须是值类型。

public class MyClass<T> where T : struct {
  public T value { get; set; }
}

 MyClass<int> m1 = new MyClass<int>();
 m1.value = 1;
 //MyClass m2 = new MyClass(); 这句代码被约束了
 Console.WriteLine(m1.value);

2、class约束:使用calss关键字可以确保泛型类型必须是引用类型(类、接口、委托或数组),而不能是值类型

 public class MyClass2<T> where T : class {
   public T obj { get; set; }
 }
//再创建一个类
public class Test { public string name; }
 
 MyClass2<Test> t1 = new MyClass2<Test>();
 //注意:这里的obj因为用了泛型,而泛型T现在是Test引用类型 ,那么obj肯定是对象。
 Console.WriteLine(t1.obj); //空
 t1.obj = new Test();
 t1.obj.name = "张三";
 Console.WriteLine(t1.obj.name); //张三 

3、class? 可为空引用类型约束:参数的值可以为null或不为null的引用类型

public class MyClass3<T> where T : class? { }

4、notnull 不能为空约束:代表不能为null的引用类型,也可以表示不能为null的值类型

public class MyClass4<T> where T : notnull { }

5、new() 构造函数约束:必须是公共无参数构造函数

public class MyClass5<T> where T : new() {
     // 当前传入的引用类型对象必须是可以用无参公共构造函数能够创建的才可以
     public T value;
}
public class Test2 {
  public Test2() {
    Console.WriteLine("我是公共无参数的构造函数test2");
  }
}

MyClass5<Test2> m5 = new MyClass5<Test2>();
m4.value = new Test2();
string str1 = "abcdefg";
string str2 = new string('c', 20);

6、多个约束放到一处

public class MyClass6<T, D> where T : class where D : struct {
	public T obj { get; set; }
    public D sum { get; set; }
}

//多个约束放在一处
//第一个参数约束Class,那么就表示在使用的时候需要赋值为引用类型
//第二个参数约束int,表示在使用的时候需要引入int值数据
MyClass3<Test, int> m3 = new MyClass3<Test, int>();
//已经确定了obj数据Test
//num属于int类型
m3.obj = new Test(); //要根据泛型的实际类型来为属性赋值
m3.obj.name = "Test";
m3.sum = 100;

7、可以对单一的泛型进行多个约束(可控),多个约束,new()要放在最后面,不能和struct和unmanaged(非托管约束)一起使用

public class MyClass5<T> where T : class, new() {
  public T Value;
}
public interface IFly {
  void fly();
}
public class Test3 : IFly {
  public void fly() {
    Console.WriteLine("实现了fly的会飞方法。");
  }
}

MyClass5<Test3> m5 = new MyClass5<Test3>();

泛型默认值(default)

当使用泛型类型时,如果需要一个默认值,可以使用 default(T) 来获取泛型类型的默认值。这个 default(T) 表达式会根据类型参数 T 的实际类型返回一个合适的默认值。

public class TestDefault<T> {
  //带有返回值的方法
  public T foo() {
    //因为在return的时候需要知道当前类型的具体值,但是泛型属于后引用,所以在这里通过default方法返回默认值
    return default(T);
  }
}

TestDefault<int> testDefault = new TestDefault<int>();
var result = testDefault.foo();
//值类型默认值是0,引用类型是null,布尔值的默认值是false,字符和字符串都是空字符 ‘\0’ “”
Console.WriteLine("这是个:" + result +"类型。");

泛型的特点:
提高代码的安全性和可读性:泛型可以在编译时进行类型检查,避免了类型转换错误,提高了代码的安全性。同时,泛型的类型参数可以提供更具有描述性的命名,增加代码的可读性。
提高代码的重用性:通过使用泛型,可以编写通用的算法和数据结构,使其适用于多种数据类型,从而提高代码的重用性。
简化代码:使用泛型可以减少代码的冗余,避免了为每种数据类型编写相似的代码。

相关链接:泛型约束总结

你可能感兴趣的:(C#,c#,开发语言)