【C#学习笔记】引用类型(1)

在这里插入图片描述


引用类型

引用类型的变量存储对其数据(对象)的引用,而值类型的变量直接包含其数据。 对于引用类型,两种变量可引用同一对象;因此,对一个变量执行的操作会影响另一个变量所引用的对象。 对于值类型,每个变量都具有其自己的数据副本,对一个变量执行的操作不会影响另一个变量(in、ref 和 out 参数变量除外)。

在前文中,我们常常说引用类型比值类型要好,一方面引用类型的对象类似于指针,不同的变量会指向同一个对象。而值类型则是将定义的值克隆一个副本赋值给新的变量。从性能方面引用类型就更好,并且从内存方面考虑,引用类型存放在CLR的虚拟机堆中,而值类型存放在栈中,在堆中可以对数据直接处理,而在栈中则需要将其前面的内存出栈,从内存方面使用引用类型也更好。而具体的关于引用和值类型在内存中的关系,将在未来GC的章节中描述。

在这里插入图片描述


class

类应该是最熟悉的一种引用类型了

class ClassA {
    
     } --直接定义类
class DerivedClass : BaseClass {
    
     } --继承单个类
class ImplClass : IFace1, IFace2 {
    
     } -- 实现两个接口
class ImplDerivedClass : BaseClass, IFace1 {
    
     } --继承一个类,实现一个接口

在C#中,我们只能继承一个类,但是可以继承多个接口。(看到本文,我默认你学过面向对象了)

匿名类

匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型。 类型名由编译器生成,并且不能在源代码级使用。 每个属性的类型由编译器推断。

var v = new {
    
     Amount = 108, Message = "Hello" };
// Rest the mouse pointer over v.Amount and v.Message in the following
// statement to verify that their inferred types are int and string.
Console.WriteLine(v.Amount + v.Message);  //108Hello

使用匿名类,我们定义新的对象的时候可以不必定义一个类,这是为了方便我们进行一些对象的生成,特别是当这个类是临时定义的,只需要属性而不需要其他方法,且有可能只会定义一次对象的时候。

匿名类型包含一个或多个公共只读属性。 包含其他种类的类成员(如方法或事件)为无效。 用来初始化属性的表达式不能为 null、匿名函数或指针类型。

在下例中,我们用prod遍历了products集合,并在每次创建一个新的匿名类,其中属性为Color和Pirce

var productQuery =
    from prod in products
    select new {
    
     prod.Color, prod.Price };

foreach (var v in productQuery)
{
    
    
    Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}

尽管我们并没有给匿名类中的属性创建变量名,但是匿名类自动的将创建时的变量名作为了自己的变量名,也就是创建时使用了prod.Color,prod.Price。它根据变量名为自己的属性也创建了相应的同名变量ColorPrice

还可以按另一种类型(类、结构或另一个匿名类型)的对象定义字段。 它通过使用保存此对象的变量来完成,如以下示例中所示,其中两个匿名类型是使用已实例化的用户定义类型创建的。 在这两种情况下,匿名类型 shipment shipmentWithBonus 中的 product 字段的类型均为 Product,其中包含每个字段的默认值。 bonus 字段将是编译器创建的匿名类型。

var product = new Product();
var bonus = new {
    
     note = "You won!" };
var shipment = new {
    
     address = "Nowhere St.", product };
var shipmentWithBonus = new {
    
     address = "Somewhere St.", product, bonus };

在上述例子中我们可以看到,匿名类本身的属性通常不需要定义类型,而类型是由编译器自己决定的。而已有类型定义的变量则是其对应的类型,例如product的类型是Product,而bonus的类型则是匿名类。
由于匿名类的类型是不定的,因此使用var关键字来定义这个匿名类。

如果想要修改匿名类中的属性,可以使用with表达式来实现:

var apple = new {
    
     Item = "apples", Price = 1.35 };
var onSale = apple with {
    
     Price = 0.79 };
var Banana = apple with {
    
    }; // 使用with空值可以直接赋值

(注意,with和record只能在C# 9以上的版本使用,unity中暂时不可用)


记录

记录是一个类或者结构体,通过添加record修饰符声明,

在下列情况下,请考虑使用记录而不是类或结构:

  • 你想要定义依赖值相等性的数据模型。
  • 你想要定义对象不可变的类型。

值相等性的意思是两个作为相等比较的值,只有当它们类型相等,且属性的属性名相等且属性值相等,才能视为相等。类似于内部的键值对需要完全相等。更具体的来说,一般而言的相等性是引用相等性,其相等比较在于:如果两个类型引用同一个变量则认为它们相等。

不可变性就是在对象实例化后禁止更改该对象的任何属性或字段值。

引用相等和值相等

为了清楚引用相等和值相等,请看下面几个例子:

Product A = new Product();
Product B = new Product();
if (A == B)
{
    
    
   Debug.Log(1);  --输出不了
}

第一个例子中,A==B是false的,因为A和B作为引用类型,它们的引用不相等

Product A = new Product();
Product B = A;
if (A == B)
{
    
    
   Debug.Log(1);  --输出1
}

在第二个例子中,B引用A,而A引用的是new Product(),所以二者引用相等。

值相等应当不必解释了,就是平常意义上的相等

record声明

var phoneNumbers = new string[2];
Person person1 = new("Nancy", "Davolio", phoneNumbers);
Person person2 = new("Nancy", "Davolio", phoneNumbers);
Console.WriteLine(person1 == person2); // output: True

person1.PhoneNumbers[0] = "555-1234";
Console.WriteLine(person1 == person2); // output: True

Console.WriteLine(ReferenceEquals(person1, person2)); // output: False

接口

接口是高度抽象的,我们可以使用interface关键字定义一个接口,在接口中我们只应当定义函数头,其内部实现则完全由继承了接口的类来实现。

interface IEquatable<T>
{
    
    
    bool Equals(T obj);
}

一个接口同样可以继承其他的接口,当一个接口继承多个其他接口后,这个接口被称为派生接口,其他接口被称为基接口。一个继承了派生接口的类,不仅需要实现派生接口中的所有方法,也需要实现基接口中的所有方法:

interface IBaseInterface
{
    
    
    void BaseMethod();
}
interface IEquatable : IBaseInterface
{
    
    
    bool Equals();
}
public class TestManager : IEquatable
{
    
    
    bool IEquatable.Equals()
    {
    
    
        throw new NotImplementedException();
    }
    void IBaseInterface.BaseMethod()
    {
    
    
        throw new NotImplementedException();
    }
}

delegate 委托

委托的使用通常需要将方法作为参数传递给其他方法时。可以将委托视为调用一个事件时会触发的方法。相当于我们不必在事件或者函数内部定义一个回调参数,而只需要把委托方法附加上去即可。

相较于接口,委托更适合灵活的方法组合,可以添加给多个事件。用我个人的理解来比喻,点餐是一个事件,假设这个饭店能APP点餐和服务员点餐,而APP只能点套餐,服务员可以一个菜一个菜给你加。现在我们分别要用委托和接口实现这个事件。委托像是有个服务员帮你点餐,你只需要说想吃什么,服务员就会帮你记下然后通知后厨。而接口像捆绑了一堆的套餐,如果点了鸡腿套餐,那一定会给你鸡腿+米饭,点了猪排套餐一定给你猪排+米饭。而使用委托,如果我们想要鸡腿+米饭,我们也可以让服务员给我们增加两个菜(方法),一个是鸡腿,一个是米饭。但是如果我们想要的菜品多,且和套餐重复的话,那么接口可以更好的实现。但如果我们想要灵活地点菜,例如我想要鸡腿+米饭,用接口实现了,然后我又想要一个猪排,如果用接口实现那接口会给我猪排+米饭。如果我只想要猪排那就应该叫服务员来,只点一个猪排。

委托可以和方法绑定,当触发委托时自动触发绑定的方法

// Declare a delegate:
delegate void Del(int x);

// Define a named method:
void DoWork(int k) {
    
     /* ... */ }

// Instantiate the delegate using the method as a parameter:
Del d = obj.DoWork;

合并委托/多路广播委托

不同的委托之间可以合并:

CustomDel hiDel, byeDel, multiDel, multiMinusHiDel;

hiDel = Hello;
byeDel = Goodbye;
multiDel = hiDel + byeDel;
multiMinusHiDel = multiDel - hiDel;

委托的合并直接使用加法即可,简单来说委托A+委托B =合并委托C,而合并委托C可以使用减法把委托A或者委托B减掉。加法就像服务员A和服务员B跟厨师说1号桌要XXX菜,2号桌要XXX菜,厨师肯定是哪桌先点餐哪桌先上菜。减法就是服务员B过来又说2号桌不要了,你别做了,那厨师只做服务员A委托的1号桌的菜就好了。

在调用多播委托时,它会按顺序调用列表中的委托。 只能合并相同类型的委托。

猜你喜欢

转载自blog.csdn.net/milu_ELK/article/details/132069060
今日推荐