C#语言入门详解;30;泛型,partial类,枚举,结构

三十、泛型,partial类,枚举,结构

在这里插入图片描述

泛型(Generic)

  • 泛型是纯技术名词,没有一点生活气息,泛型超级重要,在面向对象设计中与接口地位相当,这一节只介绍泛型中最重要的部分。
  • 正交性:我们的泛型与其他的类型实体都有正交点,泛型与类有正交,于是得到了泛型类;与接口有正交,于是得到了泛型接口,同理还有泛型委托泛型方法泛型属性泛型字段等等,导致编程的时候泛型无处不在。
    在这里插入图片描述
  • 泛型全称应该叫泛化数据类型,生活当中例子:放松时喜欢听音乐,这里音乐其实就是一个泛化的类型,没有指明具体的音乐类型,如果有一天累了,真的想要找一段音乐来听一听,这时候就需要找一段具体的音乐来播放,我才能听到这个音乐,这时候具体的音乐片段就不再是泛泛而谈了,而是具体的可以播放的音乐,这时候就叫做具体化或者叫做特化泛化和特化是相对的。这也说明了:泛型的东西在编程的时候是不能拿来直接使用的,必须经过特化之后才能拿来编程

泛型类-实例

下面看具体的例子:
实例背景:假设我是一个小商店的老板,小商店一开始特别的小,我只卖苹果,每个买苹果的我都给他一个小盒子装起来

using System;

namespace HelloGeneric
{
    public class Program
    {
        static void Main(string[] args)
        {
            Apple apple = new Apple() { Color = "Red"};
            Box box = new Box() { Cargo = apple};
            Console.WriteLine(box.Cargo.Color);
        }
    }
    class Apple
    {
        public string Color { get; set; }
    }
    //每次买苹果,我都把苹果给装到一个小盒子里给带回去
    class Box//因为这个盒子的用途很明确,只用来装苹果,所以准备一个苹果类型的属性
    {
        public Apple Cargo { get; set; }
    }
}

后来随着商店的发展,我卖的东西也越来越多,开始卖书,我也用盒子给他装起来,这时就出现问题了,第一种解决方法:为Book也创建同一个Box类型

using System;

namespace HelloGeneric
{
    public class Program
    {
        static void Main(string[] args)
        {
            Apple apple = new Apple() { Color = "Red"};
            AppleBox box = new AppleBox() { Cargo = apple};
            Console.WriteLine(box.Cargo.Color);
        
            Book book = new Book() { Name = "New Book"};
            BookBox bookBox = new BookBox() { Cargo = book };
            Console.WriteLine(bookBox.Cargo.Name);
        }
    }
    class Apple
    {
        public string Color { get; set; }
    }
    class Book
    {
        public string Name { get; set; }
    }
    //每次买苹果,我都把苹果给装到一个小盒子里给带回去
    class AppleBox//因为这个盒子的用途很明确,只用来装苹果,所以准备一个苹果类型的属性
    {
        public Apple Cargo { get; set; }
    }
    class BookBox
    {
        public Book Cargo { get; set; }
    }
}

但是这样程序就已经出问题了,除了叫做类型膨胀的问题,未来随着商品越来越多,照着这个趋势,类型将会越来越多,不好维护,下面改成在Box里面有多种商品类的属性的方案:

using System;

namespace HelloGeneric
{
    public 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; }
    }
}

这样会造成第二个问题,叫做成员膨胀,如果未来有1000种商品,那么我的Box里面就得有1000个属性,每次只用一个,其余999个属性都是用不着的,而且每次增加商品的时候,我都要去更改Box类,为它添加新的属性,如果忘了为某个商品增加属性就会导致bug。下面尝试将Box的属性改为Object类型:
在这里插入图片描述
在这里插入图片描述
这种方案在想拿出来装的什么东西的时候就变得很麻烦了,这并不是一个很好的解决方案,下面有请泛型登场:

  • 把普通类改装成泛型类的方法就是在类名后面加<>,里面写上类型参数,这个类型参数就是一个标识符,代表着一个泛化的类型,比如填音乐。TCargo是一个商品的类型
    class Box<TCargo>
    {
        public TCargo Cargo { get; set; }
    }

现在就有了一个泛型的Box类,下面去使用

  • 泛型目前有两种,一种是java那种用object替换的泛型,拿出来的时候jvm用了强转,一种是C#这种占位符,算是一种只存在于运行时的特殊类。泛型类在经过特化之后,它里面凡是使用到我们类型参数的地方,他都是强类型(强类型:变量类型不能轻易改变的)的。
    在这里插入图片描述

泛型接口-实例

下面讲一个泛型接口的例子:

using System;

namespace HelloGeneric
{
    public class Program
    {
        static void Main(string[] args)
        {
            Student<int> student = new Student<int>();
            student.ID = 101;
            student.Name = "Timothy";
        }
    }
    interface IUnique<TId>//Unique是唯一的意思,我怎么保证对象的唯一呢:需要让这个对象具有ID这个属性,ID这个属性是什么类型的呢,这时候还不确定,所以将接口改成泛型接口
    {
        public TId ID { get; set; }
    }
    //在我的系统当中有学生这个类,学生是由ID的,现在让学生类实现IUnique接口
    class Student<TId> : IUnique<TId>//如果一个类实现了泛型接口,那么这个类也是泛型的
    {
        public TId ID { get ; set ; }//这里TId之影响Student类的ID属性
        public string  Name { get; set; }
    }//现在就有了一个泛型的Student类
}

  • 如果一个类实现了泛型接口,那么这个类也是泛型的,就成了泛型类;还有另外一种实现泛型接口的方法,就是当我们在实现这个泛型接口的时候呢,我们实现的是一个特化了之后的泛型接口,这时候我们的类就不再是泛型类了:
using System;

namespace HelloGeneric
{
    public class Program
    {
        static void Main(string[] args)
        {
            Student student = new Student();
            student.ID = 0000000000000001;
            student.Name = "Timothy";
        }
    }
    interface IUnique<TId>//Unique是唯一的意思,我怎么保证对象的唯一呢:需要让这个对象具有ID这个属性,ID这个属性是什么类型的呢,这时候还不确定,所以将接口改成泛型接口
    {
        public TId ID { get; set; }
    }
    //在我的系统当中有学生这个类,学生是由ID的,现在让学生类实现IUnique接口
    class Student : IUnique<ulong>//在实现这个接口的时候就特化了,我们的类实现的是一个特化了的接口
    {
        public ulong ID { get ; set ; }
        public string Name { get; set; }
    }
}

泛型的重要性

  • 在.NET FrameWork中,几乎所有常用的数据结构都是泛型的。怎么理解呢?我们编程就是在处理数据,其实在处理数据的时候,大量的数据都是存储在各种各样的集合当中的,常见的集合有:数组、列表、链表、字典等等,而这些集合或者说数据结构都是泛型的,不光他们自己是泛型的,他们的基接口基类也都是泛型的。
  • 下面演示几个小例子:
    首先这些泛型的集合或数据结构,以及他们的泛型基接口、基类,都存放在这个名称空间里using System.Collections.Generic;
    • List泛型类的背后维护着一个数组,他可以不停的往数组里面放东西,当放的东西太多超过数组的长度的时候,他会生成一个新的更长的数组,把旧的数组里面的内容copy到新的数组里面,所以C#里面的List就是ArrayList,我们知道数组的长度是不可以改变的,而ArrayList的长度是可以改变的,所以有时也管List叫做动态数组
IList<int> list = new List<int>();
//IList<int> list为带有一个类型参数的ILIst泛型接口
//new List<int>()为带有一个类型参数的泛型类

在这里插入图片描述
IEnumerable意味着list是可以被迭代的;collection意味着是一个集合,可以往这个集合添加和移除元素
在这里插入图片描述
很多泛型类型都带有不止一个类型参数

using System;
using System.Collections.Generic;

namespace ConsoleApp2
{
    internal class Programe
    {
        public static void Main(string[] args)
        { 
            //带有不止一个类型参数的泛型接口
           IDictionary<int,string> dict = new Dictionary<int,string>();
            dict[1] = "Timothy";
            dict[2] = "Micheal";
            Console.WriteLine($"Student #1 is {dict[1]}");//用字符串模板
            Console.WriteLine($"Student #2 is {dict[2]}");//用字符串模板
        }
    }
}

接下来再看一个有关泛型的例子:

  • 现代编程最重要的三大技能:算法、数据结构、面向对象
    • 面向对象方面:泛型的正交性特别好,在面向对象几乎会影响到所有的编程实体,所以跟面向对象是有关系的
    • 数据结构方面:泛型与数据结构是密不可分的,因为集合就是数据结构,数据结构大部分也都是集合
    • 算法方面:程序之所以能运行,就是靠的我们的算法,我们用算法去处理数据,在我们的C系语言(C、C++、C#、Java)中,算法的表现形式就是函数,函数在面向对象编程的时候又叫做方法,于是得到一个推论:如果C#语言中的方法可以是泛型的,那么就证明了算法可以是泛型的。

泛型方法-实例

下面见实例:

using System;
using System.Collections.Generic;

namespace ConsoleApp2
{
    internal class Programe
    {
        public static void Main(string[] args)
        {
            int[] a1 = { 1, 2, 3, 4, 5 };
            int[] a2 = { 1, 2, 3, 4, 5, 6 };
            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);
            Console.WriteLine(string.Join(",", result));
        }

        //此方法含义:你给我两个整形数组,我把它们给合并在一起,像拉拉链一样
        static int[] Zip(int[] a,int[] b)
        {
            int[] zipped = new int[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;
        }
    }
}

此时Zip函数可以对int类型的a1,a2操作,但是无法对double类型的a3,a4操作,除非复制一个double参数的Zip函数,由于方法也是类的成员,此时又会造成类成员膨胀
下面介绍一个快捷操作:Ctrl+H查找替换
在这里插入图片描述

  • 一旦出现了方法成员膨胀的话,他比属性和字段成员膨胀更加危险,在为在这两个互为重载的方法当中绝大部分逻辑是重复的,如果我想升级其中一个方法的逻辑的话,那么另一个方法中的逻辑也要做相应的升级,bug也是类似,一旦有某一个放了去升级或修bug,问题就埋藏在代码中了,这时候就需要泛型登场了:
    • 把所有int类型数组改成泛型的
    • 泛型方法在调用的时候类型参数的自动推断
using System;
using System.Collections.Generic;

namespace ConsoleApp2
{
    internal class Programe
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Hello World");
            int[] a1 = { 1, 2, 3, 4, 5 };
            int[] a2 = { 1, 2, 3, 4, 5, 6 };
            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 };
            Console.WriteLine("Hello World2");
            var result = Zip(a1, a2);//这里传a1、a2的时候并没有显式的特化,它能够识别出来是什么类型的
            var result2 = Zip<double>(a3, a4);//这是显式的,这里<double>是灰的,说明显式的没有用上。

            Console.WriteLine("Hello World3");
            Console.WriteLine(string.Join(",", result));
        }

        //此方法含义:你给我两个整形数组,我把它们给合并在一起,像拉拉链一样
        static T[] Zip<T>(T[] a,T[] b)//把泛型参数加在方法名的后面,将所有的int[]替换成T[]
        {
            T[] zipped = new T[a.Length + b.Length];
            int ai = 0, bi = 0, zi = 0;
            Console.WriteLine(ai);
            do
            {
                if (ai < a.Length)
                {
                    zipped[zi++] = a[ai++]; 
                }
                if (bi < b.Length)
                {
                    zipped[zi++] = b[bi++];
                }
            } while (ai < a.Length || bi < b.Length);
            Console.WriteLine(ai);
            return zipped;
        }
    }
}

泛型委托-实例

  • .NETFrameWork中准备了很多泛型委托,很常用,常常会和λ表达式一起配合形成LINQ查询。最常用的两个泛型委托:Action泛型委托和Func泛型委托。
    在这里插入图片描述
  • Action<>他的类型参数是要求你告诉这个Action委托,未来你要引用的这个方法他的参数类型是什么,以及有几个参数。Action委托只能去引用没有返回值的方法。
using System;

namespace ConsoleApp4
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Action<string> a1 = Say;
            a1.Invoke("Timothy");//通过委托调用是间接调用
            Action<int> a2 = Mul;
            Mul(4);
           
        }
        static void Say(string str)
        {
            Console.WriteLine($"Hello,{str}");
        }
        static void Mul(int x)
        {
            Console.WriteLine(x*100);

        }
    }
}

  • 如果方法有返回值的话,就该使用Func委托。Func<>委托的类型参数分别指示的是你有多少个参数以及每个参数是什么类型的,而最后一个类型参数用来指明这个函数的返回值类型是什么。
using System;

namespace ConsoleApp4
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Func<int, int, int> func1 = Add;
            var result = func1(100, 200);
            Console.WriteLine(result) ;
            Func<double,double,double>func2= Add;
            var result2 = func2(10, 30);
            Console.WriteLine(result2);
           
        }
        static int Add(int a, int b)
        { 
        return a + b;
        }
        static double Add(double a, double b)
        {
            return a +2* b;
        }
    }
}

泛型委托是如何和λ表达式一起使用的

λ表达式就是对于这些逻辑非常简单的方法,我不想去声明它,而是在调用的时候,随调用随声明,而且是匿名的声明。不想让方法的名字去污染整个名称空间,想让这人名字给更重要的函数去用。

static int Add(int a, int b)
        { 
        return a + b;
        }
        static double Add(double a, double b)
        {
            return a +2* b;
        }

两个Add方法用λ表达式的方式声明如下:
在这里插入图片描述
以下两句为λ表达式:

(int a,int b)=> { return a + b; }
(double a,double b)=> { return a + b; }

由于泛型委托已经特化,所以λ表达式ab前面的类型也可以省略:
在这里插入图片描述

partial类

  • 什么叫partial类呢?
    • 说白了就是C#编译器允许我们把一个类的代码分成两部分或者多部份来编写,而且每个部分都可以以自己的速度进行版本更新。
  • 那么为什么需要partial类呢?
    • partial类可以帮助我们减少派生类,前面继承的时候说过了,我们要把那些不变的内容写在基类里,而把那些经常改变的内容写在子类里,这样就会出现一个问题:一个类当中一旦有一部分代码需要改变,我们就要为他声明一个派生类,如果改变的部分比较多的话,我们就要声明多个/多层派生类,这样就会让我们的派生结构非常的复杂。而有了partial类就非常好办了,我们可以把一个类的代码给它切成好几块,每一块是自己的一个逻辑单元,然后这个逻辑单元可以按照自己的进度去进行版本更新,最终这几块合起来还是同一个类,而且还有一个附带的好处:一般情况下,派生类的名字和基类的名字都是不一样的,使用partial类就不用为派生类想名字,而且这几块合起来之后还是原来的类名。

paitial类-实例

  • 第一个例子为关于使用Entity FeameWork来访问数据库:
    在这有一个叫做Bookstore的数据库(SQL server数据库),在这里面有一张叫做Book的表,在这张表里有四本书,表格有三列,一会Entity FeameWork会把这三列,给映射册成Book这个类的三个属性
    在这里插入图片描述
    partial类还允许类的不同部分用不同的编程语言来编写。
    partial类这一部分参见视频,用的不多,但是是现代.net编程的基石之一,非常重要

第二节、枚举类型和结构体

在这里插入图片描述

  • 枚举类型本质就是人为限制了取值范围的几个整数。
  • 结构体在讲值类型的时候提过,最典型的值类型就是结构体类型,当我们使用接口类型的变量或者object类型的变量来引用一个结构体类型的实例的时候,会产生装箱,装箱之后还可以再进行拆箱。
  • 结构体是可以实现接口的,但是不能由其它的类或结构体派生而来,也就是说结构体可以有自己的基接口,但是不能有自己的基类或基结构体。
  • 语法上也不允许结构体有自己显式的不带参数的构造器

枚举类型

快捷操作:按住Alt,左键鼠标拉一下,可以快速在多行的同一个位置添加内容

using System;

namespace ConsoleApp4
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Person person = new Person();
            person.Level = Level.Employee;
            Person boss = new Person();
            boss.Level = Level.Boss;
            Console.WriteLine(boss.Level>person.Level);
            Console.WriteLine((int)Level.Employee);//0默认,还可以改
            Console.WriteLine((int)Level.Manager);//1
            Console.WriteLine((int)Level.Boss);//2
            Console.WriteLine((int)Level.BigBoss);//3
        }
        enum Level
        { 
        Employee=100,
        Manager=200,
        Boss=300,
        BigBoss=400,
        }
        class Person
        {
            public int ID { get; set; }
            public string  Name { get; set; }
            public Level Level  { get; set; }
        }
    }
}

在这里插入图片描述

比特位式的用法

本质就为C语言中的位操作
在这里插入图片描述

结构体类型

结构体为值类型,值类型的特点是与值类型变量相关联的那块内存里存的就是这个值类型的实例(存放在栈中)。

using System;

namespace ConsoleApp4
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Student student = new Student() { ID = 101,Name = "Timothy" };
            object obj = student;//装箱
            Student student2 = (Student)obj;//拆箱
            Console.WriteLine($"#{student2.ID},Nmae:{student2.Name}");
        }
    }
    struct Student
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }
}

经典面试题:引用类型变量之间赋值的时候,变量之间copy的是对同一个对象的引用,而值类型copy的是一个完整的对象。

using System;

namespace ConsoleApp4
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Student student1 = new Student() { ID = 101,Name = "Timothy" };
            Student student2 = student1;//这里student2只是对student1的全盘复制,其本身和student1再无任何关联
            student2.ID = 102;
            student2.Name = "Michael";
            Console.WriteLine($"#{student1.ID},Name:{student1.Name}");
        }
    }
    struct Student
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }
}

结构体可以实现接口

using System;

namespace ConsoleApp4
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Student student1 = new Student() { ID = 101,Name = "Timothy" };
            Student student2 = student1;//这里student2只是对student1的全盘复制,其本身和student1再无任何关联
            student1.speek();
        }
    }
    interface ISpeek
    {
        void speek();
    }
    struct Student:ISpeek
    {
        public int ID { get; set; }
        public string Name { get; set; }

        public void speek()
        {
            Console.WriteLine($"I'm #{ID}, student {Name}");
        }
    }
}

不允许结构体有自己显式的不带参数的构造器
这就是显示无参构造器,但是可以有显式有参构造器。struct构造函数必须为所有属性赋值
在这里插入图片描述

using System;

namespace ConsoleApp4
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Student student1 = new Student(101, "timothy");
            student1.speek();
        }
    }
    interface ISpeek
    {
        void speek();
    }
    struct Student:ISpeek
    {
        public Student(int id,string name)
        {
            this.ID = id;
            this.Name = name;
        }
        public int ID { get; set; }
        public string Name { get; set; }

        public void speek()
        {
            Console.WriteLine($"I'm #{ID}, student {Name}");
        }
    }
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值