C#入门详解 刘铁猛 泛型、部分类、枚举、结构体

参考,补充C#学习笔记(二十五)泛型 CSDN博主:xiaoyaolangwj

//情景假设  水果店老板 芜湖~
//刚开始店很小,只卖苹果  Apple类, 用盒子装苹果 Box
class Program
  {
      static void Main(string[] args)
      {
          //Apple apple = new Apple() { Color = "Red" };

          Apple apple = new Apple() { Color = "Green" }; //实例化一个apple对象。
          Box box = new Box() { Cargo = apple };
          Console.WriteLine(box.Cargo.Color);
      }

      class Apple
      { 
      public string Color { get; set; }  //Apple的一个属性
      }
      class Box
      { 
      public Apple Cargo { get; set; }  //Box类中  所装货物的属性    苹果类的 货物
      }
  }

随着时间的推移,商店越来越大,我开始卖书。也用一个装书的盒子包装起来。

        static void Main(string[] args)
        {
            //Apple apple = new Apple() { Color = "Red" };

            Apple apple = new Apple() { Color = "Green" }; //实例化一个apple对象。
            AppleBox applebox = new AppleBox() { Cargo = apple };

            Console.WriteLine(applebox.Cargo.Color);


            Book book = new Book() { Name = "牧羊少年的奇幻之旅" };
            BookBox bookBox = new BookBox() { Cargo = book };
            Console.WriteLine(bookBox.Cargo.Name);
        }

        class Apple
        { 
        public string Color { get; set; }  //Apple的一个属性
        }

        class Book
        { 
        public string Name { get; set; }
        }
        class AppleBox
        { 
        public Apple Cargo { get; set; }  //Box类中  所装货物的属性    苹果类的 货物
             }


        class BookBox
        { 
        public Book Cargo { get; set; }
        }

    }

此时的问题:类型膨胀。如果商品持续增多,一千个商品,就需要有一千个类。那么需要准备的盒子也相应变多。

此时我们的想法是准备一种盒子,然后盒子中有各种各样不同类型的属性。 (寄,看了复制来的,就是没印象,重写(抄)一遍)

class Program
{
    static void Main(string[] args)
    {
        Apple apple = new Apple(){Color = "Red"};
        Book book = new Book(){Name = "New Book!"};
        Box box1 = new Box(){Apple = apple};
        Box box2 = new Box(){Book = book};
    }
}
 
class Apple
{
    public string Color { get; set; }
}
 
class Book
{
    public string Name { get; set; }
}
 
class Box
{
    public Apple Apple { get; set; }
    public Book Book { get; set; }
    //...
}

此时的问题:成员膨胀。对于box1实例来说Book属性对它是多余的。对box2实例来说,Apple属性也没用。

如果有一千件商品,那么在box类中就需要有一千个属性。而对每个实例而言,其他的999个属性没有用。

而且如果新增商品,就需要修改Box这个类,删除商品也需要修改Box类。管理上容易混乱。

思考解决方案:因为所有属性都有自己的类型,所以,我们想如果使用object类型那么是不是只要一个属性就可以了? (为了减少一个类中有非常多的属性,但是每次只调用一个属性的情况。)

      static void Main(string[] args)
        {

            Apple apple = new Apple() { Color = "Green" }; //实例化一个apple对象。
            Book book = new Book() { Name = "New Book!" };
        
            Box box1 = new Box() { Cargo = apple };
            Box box2 = new Box() { Cargo = book };
        Console.WriteLine(box1.Cargo.);  //如果想获取苹果的颜色 
        }


 class Box
        {
            //public Apple apple { get; set; }
            //public Book book { get; set; }
            public Object Cargo { get; set; }
        }

此时的问题是:因为Cargo是Object类型,没有color这个属性。

此时如果想查看Color属性怎么办?1、强制类型转换;2、使用as操作符。

Console.WriteLine((box1.Cargo as Apple)?.Color); //如果想获取苹果的颜色

请特别留意:这行的问号点(?.)。如果是则成员访问。如果不是,则什么不做。

在C#中??和?分别是什么意思?

  1. 可空类型修饰符(?):
    引用类型可以使用空引用表示一个不存在的值,而值类型通常不能表示为空。
    例如:string str=null; 是正确的,int i=null; 编译器就会报错。
    为了使值类型也可为空,就可以使用可空类型,即用可空类型修饰符"?"来表示,表现形式为"T?"
    例如:int? 表示可空的整形,DateTime? 表示可为空的时间。
    T? 其实是System.Nullable(泛型结构)的缩写形式,也就意味着当你用到T?时编译器编译 时会把T?编译成System.Nullable的形式。
    例如:int?,编译后便是System.Nullable的形式。

【没有看明白怎么用的?.】
CSDN博主「mmas01」 原文链接 C#中 ??、 ?、 ?: 、?.、?[ ]

4.NULL检查运算符(?.)
  例如我们要获取一个Point序列的第一个点的X坐标,第一感觉会这么写:
int firstX = points.First().X;
但是,老鸟会告诉你,这儿没有进行NULL检查,正确的版本是这样的:

int? firstX = null;
if (points != null)
{
var first = points.FirstOrDefault();
if (first != null)
firstX = first.X;
}

  正确倒是正确了,代码取变得难读多了。在C# 6.0中,引入了一个 ?. 的运算符,前面的代码可以改成如下形式:

int? firstX = points?.FirstOrDefault()?.X;
1
从这个例子中我们也可以看出它的基本用法:如果对象为NULL,则不进行后面的获取成员的运算,直接返回NULL

需要注意的是,由于"?.“运算符返回的可以是NULL,当返回的成员类型是struct类型的时候,”?.“和”."运算符的返回值类型是不一样的。

Point p = new Point(3, 2);
    Console.WriteLine(p.X.GetType() == typeof(int)); //true
   Console.WriteLine(p?.X.GetType() == typeof(int?)); //true

————————————————

泛型!

把普通类改装成泛型类的方法:
类型名后面加上一对尖括<>,尖括号中写上类型参数。
类型参数其实就是一个标识符,标识符代表着一个泛化的类型。
因为我们类中的属性是Cargo,是商品的名,所以我们可以写上TCargo商品的类型。

类体内当我们声明属性时:就需要TCargo类型的属性了。

  class Box<TCargo>
    {
        public TCargo Cargo { get; set; }
    }

此时获得一个泛型的Box类。

我们应该如何使用泛型的Box类?

前面提到所有泛型编程实体都不能直接拿来编程。使用之前,必须特化。

  static void Main(string[] args)
        {
     Apple apple = new Apple() { Color = "Green" }; //实例化一个apple对象。
            Book book = new Book() { Name = "New Book!" };
        
            Box<Apple> box11 = new Box<Apple>() { Cargo = apple };
            Box<Book> box2 = new Box<Book>() { Cargo = book };
            Console.WriteLine(box11.Cargo.Color);
        }

很多泛型类型的类或者接口都带有不止一个类型参数。比如IDictionary<int, string>。

            IDictionary<int, string> dict = new Dictionary<int, string>();
            dict[1] = "Timothy";
            dict[2] = "Michael";
            Console.WriteLine($"Student #1 is{dict[1]}");
            Console.WriteLine($"Student #2 is{dict[2]}");

泛型方法-泛型算法

      int[] a1 = { 1, 2, 3, 4, 5, 6, 7 };
            int[] a2 = { 1, 2, 3, 4, 5, 6, 7 };
            double[] a3 = { 1.1, 2.2, 3.3, 4.4, 5.5 };
            double[] a4 = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6 };
            // var result = Zip(a1, a2);
            var result = Zip(a3, a4);
            Console.WriteLine(string.Join(",", result));


static T[] Zip<T>(T[] a, T[] b)  //把类型参数加在 方法名后面
        {
            T[] zipped = new T[a.Length + b.Length];
            int ai = 0, bi = 0,zi = 0;
            do
            { if (ai < a.Length) zipped[zi++] = a[ai++];
                if (bi < b.Length) zipped[zi++] = b[bi++];
            }
            while (ai < a.Length || bi < b.Length);

            return zipped;
        }

image



泛型约束是什么

泛型约束是一种限制泛型类型参数的方法,在定义泛型类或泛型方法时可以使用它来指定某个泛型类型参数必须符合特定的条件,从而保证编译器在编译时能够检查出代码中的类型错误。

泛型约束可以使用以下关键字来定义:

  1. where T : class - 表示T必须为引用类型,在实例化泛型类型参数T时,只能传递引用类型参数,不能使用值类型参数;
  2. where T : struct - 表示T必须为值类型,在实例化泛型类型参数T时,只能传递值类型参数,不能使用引用类型参数;
  3. where T : new() - 表示T必须有一个公共的无参构造函数,在实例化泛型类型参数T时,可以使用new()语法创建对象;
  4. where T : 基类名 - 表示T必须派生自基类,其中基类名是限制条件中的基类名称;
  5. where T : 接口名 - 表示T必须实现接口,其中接口名是限制条件中的接口名称;
  6. where T : U - 表示T必须派生自U,在实例化泛型类型参数T时,可以传递任何从U派生的参数。

通过使用这些约束,我们可以在泛型代码中按类型进行操作,并且可以确保类型参数满足我们的需求,避免了类型转换等运行时错误。

下面是几个简单的C#代码例子

  1. 使用where约束T必须为引用类型
public class MyClass<T> where T : class
{
    private T _myVar;

    public void SetMyVar(T value)
    {
        _myVar = value;
    }
}

在这个例子中,泛型类MyClass中的类型参数T必须是引用类型,可以使用赋值语句来设置_myVar成员变量的值。

  1. 使用where约束T必须实现某个接口
public class MyClass<T> where T : IEnumerable
{
    public void Loop(IEnumerable collection)
    {
        foreach (var item in collection)
        {
            // ...
        }
    }
}

在这个例子中,泛型类MyClass中的类型参数T必须实现IEnumerable接口,可以调用Loop方法遍历collection集合中的元素。

  1. 使用多重where约束,T必须实现多个接口并且有一个无参构造函数
public class MyClass<T> where T : IDisposable, ICloneable, new()
{
    public void DoSomething(T obj)
    {
        // ...
    }
}

在这个例子中,泛型类MyClass中的类型参数T必须同时实现IDisposable和ICloneable接口,并且必须有一个公共的无参构造函数,可以调用DoSomething方法,在obj对象上执行某些操作。

Q:public abstract class SingletonMono :MonoBehaviour where T: SingletonMono 这个where T:后面为什么不是"class"而是SingletonMono

A:这是因为在泛型类中使用了泛型约束,这个约束可以对泛型类型参数进行限制。在这个例子中,where关键字后面的约束是 T: SingletonMono,它表示类型参数T必须是SingletonMono或其子类。

这种约束称为自身约束(Self Constraint),它要求T是一种继承自SingletonMono的类型。这意味着只有从SingletonMono派生出来的类型才能作为T的值传递给该类。在这个例子中,我们要求T必须是当前定义的SingletonMono泛型类或它的子类,这样就可以保证Instance()方法返回的都是正确的单例实例。

如果使用"class"作为泛型约束,则无法限制类型参数T必须是当前定义的SingletonMono泛型类或其子类,也就无法确保Instance()方法返回的是正确的单例实例。

posted @ 2023-04-03 16:18  专心Coding的程侠  阅读(326)  评论(0编辑  收藏  举报