这篇文章上次修改于 270 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
泛型,Partial类,枚举,结构
泛型(generic)无处不在
为什么需要泛型: 避免成员膨胀或者类型膨胀
正交性: 泛型类型(类/接口/委托/...),泛型成员(属性/方法/字段/...)
类型方法的参数推断
泛型与委托,lambda表达式
Partial类
减少类的派生
partial类与Entity Framework
partial类与Windows Forms,WPF,ASP.NET Core
泛型使用例其一
泛型与类的正交
现在我们开了一个小商店,我们最开始只卖苹果,用Box来装苹果....
using System;
namespace ConsoleApplication1
{
class Program
{
public static void Main(string[] args)
{
Apple apple = new Apple(){Color = "Red"};
Box box = new Box(){Cargo = apple};
}
}
class Apple
{
public string Color { get; set; }
}
class Box
{
public Apple Cargo { get; set; }
}
}
随着生意越做越好,商店开始出售更多的商品...
namespace ConsoleApplication1
{
class Program
{
public static void Main(string[] args)
{
Apple apple = new Apple(){Color = "Red"};
Book book = new Book(){Name = "Book"};
BookBox box1 = new BookBox() { Cargo = book };
AppleBox box2 = new AppleBox() { Cargo = apple };
}
}
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 ConsoleApplication1
{
class Program
{
public static void Main(string[] args)
{
Apple apple = new Apple(){Color = "Red"};
Book book = new Book(){Name = "Book"};
Box box1 = new Box() { Book = book };
Box box2 = new Box() { Apple = apple };
}
}
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; }
}
}
这次我们把各种货物变量都作为了Box的属性,可是这遇到又一个问题 -- 成员膨胀
那么....我们以这一种方式呢?
namespace ConsoleApplication1
{
class Program
{
public static void Main(string[] args)
{
Apple apple = new Apple(){Color = "Red"};
Book book = new Book(){Name = "Book"};
Box box1 = new Box() { Cargo = book };
Box box2 = new Box() { Cargo = apple };
Console.WriteLine((box1.Cargo as Book)?.Name);
Console.WriteLine((box2.Cargo as Apple)?.Color);
}
}
class Apple
{
public string Color { get; set; }
}
class Book
{
public string Name { get; set; }
}
class Box
{
public Object Cargo { get; set; }
}
}
我们想到利用C#语言的单根性去使用Object类型变量来引用各种货物...然后又遇到一个问题 -- 访问货物属性困难
于是,我们想到了泛型
namespace ConsoleApplication1
{
class Program
{
public static void Main(string[] args)
{
Apple apple = new Apple(){Color = "Red"};
Book book = new Book(){Name = "Book"};
Box<Apple> box1 = new Box<Apple>() { Cargo = apple };
Box<Book> box2 = new Box<Book>() { Cargo = book };
Console.WriteLine(box1.Cargo.Color);
Console.WriteLine(box2.Cargo.Name);
}
}
class Apple
{
public string Color { get; set; }
}
class Book
{
public string Name { get; set; }
}
class Box<TCargo>
{
public TCargo Cargo { get; set; }
}
}
像这样,每次用具体传入类型替换泛型,可以方便的用Box装各种货物并访问它们的属性.
泛型使用例其二
泛型与接口的正交
( 1.1 ) 泛型ID接口
using System;
namespace HelloGeneric
{
class Program
{
public static void Main(string[] args)
{
Student<ulong> student = new Student<ulong>();
student.ID = 10000000000000001;
student.Name = "Yo";
}
}
interface IUnique<TId>
{
TId ID { get; set; }
}
class Student<TId> : IUnique<TId>
{
public TId ID { get; set; }
public string Name { get; set; }
}
}
接口限制学生要有独有的ID属性,但不限制ID的类型.
( 1.2 ) 泛型ID接口特化
using System;
namespace HelloGeneric
{
class Program
{
public static void Main(string[] args)
{
Student student = new Student();
student.ID = 10000000000001;
student.Name = "Yo";
}
}
interface IUnique<TId>
{
TId ID { get; set; }
}
class Student : IUnique<ulong>
{
public ulong ID { get; set; }
public string Name { get; set; }
}
}
学生直接使用特化之后的泛型接口.
(2.1) 泛型特化方法
返回值是泛型,传入的参数也是泛型.这里使用Zip方法时不用显式使用<类型>
调用方法,因为此时编译器会自动推断.
泛型使用例其三
泛型与委托的正交
(1.1) 泛型委托
using System;
using System.IO.Compression;
namespace HelloGeneric
{
class Program
{
public static void Main(string[] args)
{
Func<double,double,double> func1 = Add;
Func<int,int,int> func2 = Add;
var result1 = func1(99.55, 200.5);
var result2 = func2(100, 200);
Console.WriteLine(result1);
Console.WriteLine(result2);
}
static int Add(int a, int b)
{
return a + b;
}
static double Add(double a, double b)
{
return a + b;
}
}
}
此处Add方法有两个重载,特化的泛型委托可以自动找到对应的方法.
(1.2) 泛型委托与lambda表达式的结合使用
因为func1委托之前引用的方法逻辑简单,可以不需要显式声明,而使用lambda表达式直接创建一个匿名方法让委托引用,避免污染名称空间.
因为委托已经指定了返回值和传入参数的类型,所以可以进一步简化.
Partial类
C#语言中,在同一个程序集中,同一个命名空间下,可以使用Partial关键字把一个类分散在多个文件中实现,之后编译器会自动将它们拼接
Partial类的强大之处
- 使用Partial类可以将一个类划分为多个部分进行维护,保证一些方法不受另一些方法更新的影响.
- 使用Partial类不同的Partial可以用不同的编程语言来编写.
WinForm
的逻辑部分和设计部分就是分在不同文件的,但使用Partial类,都使用C#语言并且最后会拼接在一起. ヽ( ̄ω ̄( ̄ω ̄〃)ゝ
WPF
的界面使用XAML语言编写界面(最后会被编译器编译为C#代码),逻辑使用C#语言.
枚举类型
- 人为限定取值范围的整数
- 整数值的对应
- 比特位式用法
namespace TryEnum
{
class _Program
{
public 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);
Console.WriteLine((int)Level.Manager);
Console.WriteLine((int)Level.Boss);
Console.WriteLine((int)Level.BigBoss);
}
}
enum Level
{
Employee,
Manager,
Boss,
BigBoss,
}
class Person
{
public int ID { get; set; }
public string Name { get; set; }
public Level Level { get; set; }
}
}
输出为
True
0
1
2
3
可以指定枚举器背后对应的值
enum Level
{
Employee = 100,
Manager = 200,
Boss = 300,
BigBoss = 400,
}
如果不给枚举值赋值,会自动在前一个枚举值基础上加一.
比如不给Manager赋值,它的值将会是101.
这就是枚举值和整数之间的对应关系.
初探枚举类型比特位用法
输出为
True
True
|
是按位或,&
是按位与.
结构体(struct)
- 值类型,可装/拆箱
- 可实现接口,不能派生自类/结构体
- 不能有显式无参构造器
结构体可实现接口,但不能有基类或基结构体,而且语法上不允许结构体有显式的不带参数的构造器
而在C# 10更新之后结构体struct有了新特性,结构体可以使用显式的无参构造器了.
不过必须是public, 而且不能是partial.
using System;
using System.IO.Compression;
using System.Threading.Channels;
namespace Try
{
class _Program
{
public static void Main(string[] args)
{
Student stu = new Student(){ID = 101,Name="ToT"};
// stu是一个局部变量 分布在main函数的函数栈上
object obj = stu; // 将stu copy到堆内存,用obj引用堆内存上的stu实例 此为装箱
Student stu2 = (Student)obj; // 此处为拆箱
Console.WriteLine($"#{stu2.ID} Name:{stu2.Name}");
}
}
struct Student
{
public int ID { get; set; }
public string Name { get; set; }
}
}
结构体为值类型,复制的时候是复制一个完整的对象.见如下代码.
namespace Try
{
class _Program
{
public static void Main(string[] args)
{
Student stu1 = new Student() { ID = 101, Name="YO" };
Student stu2 = stu1;
stu2.ID = 1001;
stu2.Name = "Zeng";
Console.WriteLine($"{stu1.ID},{stu1.Name}");
Console.WriteLine($"{stu2.ID},{stu2.Name}");
}
}
struct Student
{
public int ID { get; set; }
public string Name { get; set; }
}
}
输出为:
101,YO
1001,Zeng
- 由此可见,与引用类型不同,作为值类型的结构体在复制时创建了一个新的对象,进行的修改作用于两个不同的对象,而没有出现像引用类型一样"两个孩子牵着一个气球"(孩子指变量,气球指被引用的对象).
结构体实现接口
using System;
namespace Try
{
class _Program
{
public static void Main(string[] args)
{
Student stu1 = new Student() { ID = 101, Name="YO" };
Student stu2 = stu1;
stu2.ID = 1001;
stu2.Name = "Zeng";
stu1.Speak();
stu2.Speak();
}
}
interface ISpeak
{
void Speak();
}
struct Student : ISpeak
{
public int ID { get; set; }
public string Name { get; set; }
public void Speak()
{
Console.WriteLine($"I am #{this.ID} student {this.Name}");
}
}
}
再次说明,结构体不能有基类和基结构体.
FROM CHATGPT 3.5
在C#和C++中,结构体(Struct)都是用来表示一组相关数据的自定义类型,但它们在语法和行为上有一些区别。
C#中的结构体和C++中的结构体区别:
C#中的结构体(Struct):
- 值类型: 在C#中,结构体是值类型,意味着当你将结构体赋值给另一个变量或作为参数传递时,实际上是在复制整个结构体的值。这与类不同,类是引用类型,赋值和传递时传递的是引用。
- 继承不支持: C#中的结构体不支持继承,它们不能从其他结构体或类派生。它们只能实现接口。
- 默认构造函数: 如果你没有为结构体定义任何构造函数,C#会为你提供一个默认的无参数构造函数。但你也可以自定义构造函数。
- 堆栈和堆: 结构体的实例可以存储在堆栈上,也可以存储在堆上,具体取决于它们是在哪里创建的。在C#中,如果在堆上创建结构体实例,需要使用
new
关键字。
C++中的结构体:
- 值类型: 在C++中,结构体也是值类型。和C#一样,将结构体赋值给另一个变量会复制整个结构体的值。
- 继承支持: C++中的结构体支持继承,可以从其他结构体或类派生。
- 默认构造函数: 如果你没有为结构体定义任何构造函数,C++会提供一个默认的无参数构造函数。你也可以自定义构造函数。
- 内存布局: 在C++中,结构体的内存布局和类非常相似,它们可以包含成员变量、函数等,而在C#中,结构体的成员限制较多。
C++中的结构体和类区别:
在C++中,结构体(struct)和类(class)的主要区别在于默认的成员访问权限:
- 默认访问权限: 在类中,默认的成员访问权限是私有(private),而在结构体中,默认的成员访问权限是公有(public)。
- 继承: 在类中,默认继承是私有继承(private inheritance),而在结构体中,默认继承是公有继承(public inheritance)。
这些差异导致在使用时,结构体更适合简单的数据集合,而类更适合包含更多行为和复杂逻辑的对象。在C++中,除了上述差异,结构体和类基本上是相似的,可以互相替代。
没有评论