(五)CSharp-类

一、类

1、类的定义和类成员

程序的数据和功能被组织为逻辑上相关的数据项和函数的封装集合,并称为类。
类是一个能存储数据并执行代码的数据结构。它包含数据成员和函数成员。

表-类成员的类型

数据成员存储数据 函数成员执行代码
字段、常量 方法、属性、构造函数、析构函数 运算符、索引器、事件
//显式和隐式字段初始化
class MyClass
{
int F1; //初始化为 0 - 值类型
string F2; //初始化为 null - 引用类型
int F3 = 25; //初始化为 25
string F4 = "abcd"; //初始化为 “abcd”
}

2、为数据分配内存

类的实例是引用类型,所以需要使用 new 运算符实例化:

  • new 运算符为任意指定类型的实例分配并初始化内存。它依据类型的不同从栈或堆里分配。
  • 如果将内存分配给一个引用类型,则对象创建表达式返回一个引用,指向在堆中被分配并初始化的对象实例。

3、属性

1)只读和只写属性

  • 只有 get 访问器的属性称为只读属性。只读属性能够安全地将一个数据项从类或类的实例中传出,而不必让调用者修改属性值。
  • 只有 set 访问器的属性称为只写属性。只写属性很少见,因为它们几乎没有实际用途。
  • 两个访问器中至少有一个必须定义,否则编译会产生一条错误消息。

2)属性与公有字段的区别

  • 属性是函数成员而不是数据成员,允许你处理输入和输出,而公有字段不行。
  • 属性可以只读或只写,而字段不行。
  • 编译后的变量和编译后的属性语义不同

C# 图解教程-7.10.7 属性与公有字段》书上附加内容:

如果要发布一个由其他代码引用的程序集,那么第三点将会带来一些影响。例如,有的时候开发人员可能想用共有字段代替属性,因为如果以后需要为字段的数据增加处理逻辑的话,可以再把字段改为属性。这没错,但是如果那样修改的话,所有访问这个字段的其他程序集都需要重新编译,因为字段和属性在编译后的语义不一样。另外,如果实现的是属性,那么只需要修改属性的实现,而无须重新编译访问它的其他程序集。

我需要提供一些代码例子来加强理解:

如果程序员对某一情况最初始的想法是只使用字段:

//程序集Stu
    class Student
    {
        public int Id;
    }

被其他程序集ABC...访问引用
class Other
{
public Other()
{
Student stu = new Student();
stu.Id = 2;//此处是字段
}
}

但是产品经理说,下版本有这样的需求:Student 的 Id 不能有负值。哦豁,如果按照以上的代码来设计,那估计其他访问Stu的所有程序集都必须做判断的逻辑处理。如果只是把Id字段改为属性类型,同样也得需要重新编译其他所有访问 Stu 的程序集才能生效,因为它们访问的类型都必须保持一致。否则,会报错。

所以,最开始就应该这么做:

   //程序集Stu 版本1
    class Student
    {
        private int id;
        public int Id
        {
            get { return id; }
            set
            {
              id = vvalue;
            }
        }
    }
    
//程序集Stu 版本2
class Student
    {
        private int id;
        public int Id
        {
            get { return id; }
            set
            {
                if (id > 0)
                    id = value;
                else
                    id = 0;
            }
        }
    }
    
其他所有程序集访问Stu
class Other
{
public Other()
{
Student stu = new Student();
stu.Id = 2;//此处是属性,对版本1和版本和2都能访问
}
}

3)自动实现属性(简称自动属性)

  • 不声明后备字段——编译器根据属性的类型分配存储。
  • 不能提供访问器的方法体——它们必须被简单地声明为分号,get 担当简单的内存读,set 担当简单的写。但是因为无法访问自动属性的方法体,所以在使用自动属性时调试代码通常会更更加困难。

4)静态属性

静态属性的访问器和所有静态成员一样,具有以下特点:

  • 不能访问类的实例成员,但能被实例成员访问。
  • 不管类是否有实例,它们都是存在的。
  • 在类的内部,可以仅使用名称来引用静态属性。
  • 在类的外部,可以通过类名或者使用 using static 结构来引用静态属性。

4、其他静态成员类型

可以声明为 static 的类成员类型做了勾选标记

表-可以声明为静态的类成员类型

数据成员(存储数据) 函数成员(执行代码)
✅字段 ✅方法
✅类型 ✅属性
常量 ✅构造函数
✅运算符
索引器
✅事件

5、静态构造函数

静态构造函数初始化类的静态字段

静态构造函数与实例构造函数的区别:

  • 1)静态构造函数声明中使用 static 关键字
  • 2)类只能有一个静态构造函数,而且不能带参数
  • 3)静态构造函数不能有访问修饰符

6、屏蔽基类的成员(隐藏)

  • 屏蔽基类的成员(数据成员,静态成员,函数成员),可使用 new 修饰符。
  • 函数成员,签名由名称和参数列表组成,不包括返回类型。

7、抽象成员

虚成员 抽象成员
关键字 virtual abstract
实现体 有实现体 没有实现体,被分号取代
在派生类中被覆写 能被覆写,使用 override 必须被覆写,使用 override
成员的类型 方法属性事件索引器 方法属性事件索引器

二、弱引用

实例化一个类或结构时,只要有代码引用它,就会形成强引用。
根据例子来理解强引用

MyClass myClassVariable = new MyClass();

myClassVariable 引用该类的对象,那么只要 myClassVariable 在作用域内,就存在对 MyClass 对象的强引用。

使用弱引用的情况:
强引用意味着垃圾回收器不会清理 MyClass 对象使用的内存。如果 MyClass 对象很大,并且不经常访问,就可以创建对象的弱引用。
弱引用允许创建和使用对象,但是垃圾回收器运行时,就会回收对象并释放内存
弱引用是使用了系统提供的 WeakReference 类创建的。

使用弱引用的例子:

  class MathTest
    {
        public int Value;
        public int GetSquare()
        {
            return Value * Value;
        }

        public static int GetSquareOf(int x)
        {
            return x * x;
        }

        public static double GetPi()
        {
            return 3.14159;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            WeakReference mathReference = new WeakReference(new MathTest());
            MathTest math;

            //检查确保MathTest对象未被回收
            //若IsAlive为true,就从目标属性得到MathTest对象的引用
            if (mathReference.IsAlive)
            {
                math = mathReference.Target as MathTest;
                math.Value = 30;
                Console.WriteLine("Value field of math variable contains " + math.Value);
                Console.WriteLine("Square of 30 is " + math.GetSquare());
            }
            else
            {
                Console.WriteLine("Reference is not available.");
            }
            //调用垃圾回收器
            GC.Collect();

            //再次尝试获取MathTest对象
            if (mathReference.IsAlive)
            {
                math = mathReference.Target as MathTest;
            }
            else
            {
                //此处说明MathTest对象已被回收。
                //如果想要再使用MathTest对象,就必须实例化一个新的MathTest对象。
                Console.WriteLine("Reference is not available.");
            }

           Console.ReadKey();
        }
    }

三、匿名类

匿名类型只是一个继承自 Object 且没有名称的类。

var captain = new { FirstName = "Leonard", MiddleName = "", LastName = "McCoy" };

如果通过另一个对象来设置初始值:

 var person = new { FirstName = "Leonard", MiddleName = "", LastName = "McCoy" };
 var captain = new { person.FirstName, person.MiddleName, person.LastName  };

四、部分类

partial 关键字允许把类、结构、方法或接口放在多个文件中。

分部方法分为两部分:

  • 定义分部方法
  • 实现分部方法

分部方法定义声明和实现声明的签名和返回类型必须匹配。签名和返回类型有如下特征:

  • ==返回类型必须是 void ==。
  • 签名不能包括访问修饰符,这使分部方法是隐式私有的
  • 在定义声明和实现声明中都必须包含上下文关键字 partial,并且直接放在关键字 void 之前。
//文件A:
  partial class MyClass
    {
        partial void PrintSum(int x, int y);

        public void Add(int x, int y)
        {
            PrintSum(x, y);
        }
    }

//文件B:
    partial class MyClass
    {
        partial void PrintSum(int x, int y)
        {
            Console.WriteLine("Sum is {0}", x + y);
        }
    }

五、静态类

如果类只包含静态的方法和属性,该类就是静态的。

  static class StaticUtilities
    {
        public static void HelperMethod()
        {

        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            StaticUtilities.HelperMethod();

           Console.ReadKey();
        }
    }

六、Object 类

所有的 .NET 类都派生自 System.Object。如果在自定义类时没有指定基类,编译器就会自动假定整个类派生自 Object。

System.Object() 方法

我在这里把书上的部分内容,进行排版,并做成一个表单的形式来显示该部分内容:

方法 描述
ToString() 是获取对象的字符串表示的一种便捷方式。⚪当只需要快速获取对象的内容,以进行调试时,就可以使用这个方法。⚪在数据的格式化方面,它几乎没有提供选择:例如,在原则上日期可以表示为许多不同的格式,但 DateTime.ToString() 没有在这方面提供任何选择。如果需要更复杂的字符串表示。例如,考虑用户的格式化首选项或区域性(区域),就应实现 IFormattable 接口。
GetHashCode() 如果对象放在名为映射(也称为散列表或字典)的数据结构中,就可以使用整个方法。处理这些结构的类使用方法确定把对象放在结构的什么地方。如果希望把类用作字典的一个键,就需要重写 GetHashCode() 方法。实现该方法重载的方式有一些相当严格的限制。
Equals()(两个版本) 和 ReferenceEquals() 主要有3个用于比较对象相等性的不同方法,这说明 .NET Framework 在比较相等性方面有相当复杂的模式。这3个方法和比较运算符 “ == ” 在使用方式上有微妙的区别。而且,在重写带一个参数的虚 Equals() 方法时也有一些限制,因为 System。Collections 名称空间中的一些基类要调用该方法,并希望它以特定的方式执行。
Finalize() 它最接近 C++ 风格的析构函数,在引用对象作为垃圾被回收以清理资源时调用它。 Object 中实现的 Finalize() 方法实际上什么也没有做,因而被垃圾回收器忽略。如果对象拥有对未托管资源的引用,则在该对象删除时,就需要删除这些引用,此时一般要重写 Finalize()。垃圾回集器不能直接删除这些对未托管资源的引用,因为它只负责托管的资源,于是它只能依赖用户提供的 Finalize()。
GetType() 这个方法返回从 System.Type 派生的类的一个实例。这个对象可以提供对象成员所属类的更多信息,包括基本类型、方法、属性等。System.Type 还提供了 .NET 的反射技术的入口点。
MemberwiseClone() 它复制对象,并返回对副本的一个引用(对于值类型,就是一个装箱的引用)。注意,得到的副本是一个浅表复制,即它复制了类的所有值类型。如果类包含内嵌的引用,就只复制引用,而不复制引用的对象。这个方法是受保护的。所以不能用于复制外部的对象。该方法不是虚方法,所以不能重写它的实现代码。

你可能感兴趣的:(CSharp,c#)