C# 学习笔记 - 2.2 - 类

发布时间 2023-08-21 20:26:11作者: Snoopy1866

与其他面向对象编程语言一样,C# 序的功能是在一个或多个类中实现的。类的方法(methods)和属性(properties)包含定义类行为方式的代码。

C# 通过封装属性和方法,以及启用多种类型的多态性(包括通过继承实现的子类型多态性(subtyping polymorphism via inheritance)和泛型的参数多态性(parametric polymorphism via generics))来实现信息隐藏(information hiding)。

各种 C# 类均可定义,包括:

  • 标准类(instance classes
  • 静态类(static classes
  • 结构体(structures

例如,以下代码定义了一个名为 Employee 的类,包含 Name 和 Age 属性,已经两个空方法 GetPayCheck() 和 Work(),并定义了一个继承自 Emplyee 的 Sample 类:

public class Employee
{
    private int _Age;
    private string _Name;

    public int Age
    {
        get { return _Age; }
        set { _Age = value; }
    }

    public string Name
    {
        get { return _Name; }
        set { _Name = value; }
    }

    public void GetPayCheck()
    {
    }

    public void Work()
    {
    }
}

public class Sample
{
    public static void Main()
    {
        Employee marissa = new Employee();

        marissa.Work();
        marissa.GetPayCheck();
    }
}

方法(Methods)

C# 方法是类的成员,可以有返回值,参数列表,有时候还有泛型(generic)声明。像字段一样,方法包括静态方法和实例方法。

  • 静态方法:通过类访问
  • 实例方法:通过类实例化后的对象访问

Like fields, methods can be static (associated with and accessed through the class) or instance (associated with and accessed through an object instance of the class) methods as well as a generic type declaration.

自 C# 4.0 以来,方法可以包含可选参数,可选参数应当设定默认值,例如:

void Increment(ref int x, int dx = 1)

构造器(Constructions of classes)

构造器控制着类的初始化,构造器通常用于设置类的属性,但并不仅限于此,与方法一样,构造器也可以有自己的参数。

public class Employee
{
    public Employee()
    {
        System.Console.WriteLine("Constructed without parameters");
    }

    public Employee(string strText)
    {
        System.Console.WriteLine(strText);
    }
}

public class Sample
{
    public static void Main()
    {
        System.Console.WriteLine("Start");
        Employee Alfred = new Employee();
        Employee Billy  = new Employee("Parameter for construction");
        System.Console.WriteLine("End");
    }

构造器之间也可以互相调用:

public class Employee
{
    public Employee(string strText, int iNumber)
    {
        ...
    }
    
    public Employee(string strText)
        : this(strText, 1234) // calls the above constructor with user-specified text and the default number
    { }
    
    public Employee()
        : this("default text") // calls the above constructor with the default text
    { }
}

终结器(Finalizers / Destructors)

与构造器相反,终结器定义了一个对象不再使用时的最后的行为,C# 的垃圾回收机制使得终结器的具体行为时间变得不可预测。

public class Employee
{
    public Employee(string strText)
    {
        System.Console.WriteLine(strText);
    }

    ~Employee()
    {
        System.Console.WriteLine("Finalized!");
    }

    public static void Main()
    {
        Employee marissa = new Employee("Constructed!");

        marissa = null;
    }
}

属性(Properties)

C# 的属性是一种使用字段(field)语法公开方法功能的类成员,它简化了传统上调用 get 和 set 方法(accessor methods)的语法,类似于方法,属性也可以是静态的或实例的。

public class MyClass
{
    private int m_iField = 3; // Sets integerField with a default value of 3

    public int IntegerField
    {
        get
        {
            return m_iField;  // get returns the field you specify when this property is assigned
        }
        set
        {
            m_iField = value; // set assigns the value assigned to the property of the field you specify
        }
    }
}

getter/setter 方法也可以使用以下更短的方式定义:

class Culture
{
    public int TalkedCountries { get; set; }
    public string Language { get; set; }
}

class InterculturalDialogue
{
    Culture culture;

    culture.Language = "Italian";  // ==> culture.SetLanguage("Italian");

    string strThisLanguage = culture.Language; // ==> ... = culture.GetLanguage();
}

索引器(Indexers)

C# 索引器是定义数组访问操作行为的类成员。
使用 this 关键字创建索引器,例如:

public class EmployeeCollection
{
    public string this[string strKey]
    {
        get { return coll[strKey]; }
        set { coll[strKey] = value; }
    }
}

EmployeeCollection e = new EmployeeCollection();

string s = e["Jones"];
e["Smith"] = "xxx";

事件(Events)

C# 事件是向类的客户端公开通知的类成员。
事件只能被触发,不能被分配。

Events are only fired and never assigned

例如以下代码:(需要了解委托才能理解)

using System;

// Note: You need to know some about delegate, properties and methods to understand this sample
namespace EventSample
{
    /// <summary>
    /// This delegate defines the signature of the appropriate method
    /// </summary>
    public delegate void ContractHandler(Employee sender);

    /// <summary>
    ///     Employee class
    /// </summary>
    public class Employee
    {
        /// <summary>
        ///     Field for the info whether or not the Employee is engaged
        /// </summary>
        private bool bIsEngaged = false;
        /// <summary>
        ///     Age of the employee
        /// </summary>
        private int iAge = -1;
        /// <summary>
        ///     Name of the employee
        /// </summary>
        private String strName = null;

        /// <summary>
        /// *** The our event *** 
        /// Is a collection of methods that will be called when it fires
        /// </summary>
        public event ContractHandler Engaged;

        /// <summary>
        ///     Standard constructor
        /// </summary>
        public Employee()
        {
            // Here, we are adding a new method with appropriate signature (defined by delegate)
            // note: when an event has no method and it was fired, it causes an exception!
            //       for all effects when programming with events, assign one private method to event
            //       or simply do a verification before firing it! --> if (event != null)
            this.Engaged += new ContractHandler(this.OnEngaged);
        }

        /// <summary>
        ///     Event handler for the "engaged" event
        /// </summary>
        /// <param name="sender">
        ///     Sender object
        /// </param>
        private void OnEngaged(Employee sender)
        {
            Console.WriteLine("private void OnEngaged was called! this employee is engaged now!");
        }

        /// <summary>
        ///     Accessor for the employee name
        /// </summary>
        public string Name
        {
            get
            {
                return strName;
            }

            set
            {
                strName = value;
            }
        }

        /// <summary>
        ///     Accessor for the employee age
        /// </summary>
        public int Age
        {
            get
            {
                return m_iAge;
            }

            set
            {
                m_iAge = value;
            }
        }

        /// <summary>
        ///     Accessor for the information about employee engagement
        /// </summary>
        public bool IsEngaged
        {
            get
            {
                return bIsEngaged;
            }

            set
            {
                if (bIsEngaged == false && value == true)
                {
                    // here we fires event (call all the methods that it have)
                    // all times when IsEngaged is false and set to true;
                    Engaged(this);
                }

                bIsEngaged = value;
            }
        }
    }

    /// <summary>
    ///     Class for the entry point
    /// </summary>
    public class EntryPointClass
    {
        static void Main(string[] a_strArgs)
        {
            Employee simpleEmployee = new Employee();

            simpleEmployee.Age = 18;
            simpleEmployee.Name = "Samanta Rock";
            
            // Here...
            // This is saying when the event fires, the method added to event are called too.
            // note that we cannot use =
            // is only += to add methods to event or -= do retire an event
            simpleEmployee.Engaged += new ContractHandler(SimpleEmployee_Engaged);
        
            // make attention here...
            // when I assign true to this property, 
            // the event Engaged will be called
            // when event is called, all method that it have, are called!
            simpleEmployee.IsEngaged = true;

            Console.ReadLine();

            return;
        }

        /// <summary>
        ///     Event handler for the registered "engaged" event
        /// </summary>
        /// <param name="sender">
        ///     Event sender
        /// </param>
        static void SimpleEmployee_Engaged(Employee sender)
        {
            Console.WriteLine("The employee {0} is happy!", sender.Name);
        }
    }
}

运算符重载(Operator overloading)

C# 运算符定义是类成员,用于定义或重新定义类实例后的的基本 C# 运算符的行为。

public class Complex
{
    private double m_dReal, m_dImaginary;
    
    public double Real
    {
        get { return m_dReal; }
        set { m_dReal = value; }
    }
    
    public double Imaginary
    {
        get { return m_dImaginary; }
        set { m_dImaginary = value; }
    }
    
    // binary operator overloading
    public static Complex operator +(Complex c1, Complex c2)
    {
        return new Complex() { Real = c1.Real + c2.Real, Imaginary = c1.Imaginary + c2.Imaginary };
    }
    
    // unary operator overloading
    public static Complex operator -(Complex c)
    {
        return new Complex() { Real = -c.Real, Imaginary = -c.Imaginary };
    }
    
    // cast operator overloading (both implicit and explicit)
    public static implicit operator double(Complex c)
    {
        // return the modulus - sqrt(x^2 + y^2)
        return Math.Sqrt(Math.Pow(c.Real, 2) + Math.Pow(c.Imaginary, 2));
    }
    
    public static explicit operator string(Complex c)
    {
        // we should be overloading the ToString() method, but this is just a demonstration
        return c.Real.ToString() + " + " + c.Imaginary.ToString() + "i";
    }
}

public class StaticDemo
{
    public static void Main()
    {
        Complex number1 = new Complex() { Real = 1, Imaginary = 2 };
        Complex number2 = new Complex() { Real = 4, Imaginary = 10 };
        Complex number3 = number1 + number2; // number3 now has Real = 5, Imaginary = 12
        
        number3 = -number3; // number3 now has Real = -5, Imaginary = -12
        double testNumber = number3; // testNumber will be set to the absolute value of number3
        Console.WriteLine((string)number3); // This will print "-5 + -12i".
        // The cast to string was needed because that was an explicit cast operator.
    }
}

结构体

使用 struct 关键字定义结构体。结构体是轻量化的类,在数据结构比较小时可以减少内存占用。在大多数情况下,使用标准类是更好的选择。

结构体与类的区别是:结构体的实例是值,类的实例是引用。因此,当结构体被当做参数传入函数时,这个类会被复制一份。

struct Employee
{
    public int m_iAge;
    private string m_strName;

    public string Name
    {
        get { return m_strName; }
        set { m_strName = value; }
    }
}

结构体构造器

结构体需要构造器,或者说,初始化器,因为初始化器仅用于初始化内存。带有参数的结构体的初始化器是不被允许的。

struct Timestamp
{
    private ushort m_usYear;
    private ushort m_usMonth;
    private ushort m_usDayOfMonth;
    private ushort m_usHour;
    private ushort m_usMinute;
    private ushort m_usSecond;

    public Timestamp(ushort usYear,
        ushort usMonth,
        ushort usDay,
        ushort usHour,
        ushort usMinute,
        ushort usSecond)
    {
        m_usYear = usYear - 1900;
        m_usMonth = usMonth;
        m_usDay = usDay;
        m_usHour = usHour;
        m_usMinute = usMinute;
        m_usSecond = usSecond;
    }
}

静态类(Static classes)

静态类通常被用于实现单例模式(Singleton Pattern)。
静态类的所有方法、属性、字段都是静态的,因此无需对静态类进行实例化即可使用这些成员。

例如(System.Console 就是一个静态类):

public static class Writer
{
    public static void Write()
    {
        System.Console.WriteLine("Text");
    }
}

public class Sample
{
    public static void Main()
    {
        Writer.Write();
    }
}