[C# Study Notes] Inheritance, encapsulation, and polymorphism of C# features

Insert image description here


Logically, inheritance and encapsulation polymorphism should be the first thing to learn in your study notes. However, this series is not for novices. The basic concepts of inheritance, encapsulation and polymorphism should be mastered. This article needs to talk about some of the features of inheritance and encapsulation polymorphism in C#.

Partially excerpted from C# study notes (2) - encapsulation, inheritance, polymorphism

encapsulation

Encapsulation is to close the attributes and methods of a class. External members cannot be called directly and can only be accessed through reserved interfaces.

access modifier

When we introduced classes by referring to objects before, we have already introduced that
access modifiers are mainly divided into four types:

  • public: Any other code in the same assembly or other assemblies that reference this assembly can access the type or member. (public properties can be inherited)
  • private: Only code in the same class or struct can access the type or member. (private properties cannot be inherited)
  • protected: Only code in the same class or a class derived from this class can access the type or member. (Only accessible to this class and its subclasses)
  • internal: Any code in the same assembly can access the type or member, but code in other assemblies cannot. In other words, an internal type or member can be accessed from code belonging to the same compilation.

We can use access modifiers to define the access type of a class
111
By default, when a class does not define an access modifier, the access level is internal. When the access modifier of the members is not defined, they are not private by default.

class Manager // 在命名空间中定义,默认为internal,可被同一程序集的另一个代码文件访问
{
    
    
    class Parent // 在类中定义的成员类,默认为private,不可被同一程序集的另一个代码文件访问
    {
    
    }
}

(Note that in the namespace, the access modifier of a class can only be internal or public, because other access modifiers cannot be accessed in the namespace, so there is no need to define it)

Static classes and static methods

Classes defined using the static modifier are called static classes. Static classes are basically the same as non-static classes, with one difference: static classes cannot be instantiated. In other words, you cannot create a variable of class type using the new operator. Since there are no instance variables, members of a static class can be accessed using the class name itself.

For example, if you have a static class named UtilityClass with a public static method named MethodA, as shown in the following example:

static class UtilityClass
{
    
    
	public static int a = 1;
	public static void MethodA() {
    
     }
}
static void main(){
    
    
	UtilityClass.MethodA();
}

Using a static class, we do not need to instantiate the object, but directly call the public method in the class. Its internal members can only be static. (In fact, ordinary classes can also access static members through class names) They
cannot be inherited or instantiated. The main purpose of static classes is to provide a container to organize and manage related static members.

static constructor

When we access the static class for the first time, the static constructor will be called once. But it won't happen after the second time, so it plays an initialization function.

static class UtilityClass
{
    
    
    public static int a = 1;
    public static void MethodA() {
    
     }
    static UtilityClass()
    {
    
    
        Debug.Log("我是静态类的静态构造函数");
    }
}
class NormalClass
{
    
    
    public static void MethodA() {
    
     }
    static NormalClass()
    {
    
    
        Debug.Log("我是普通类的静态构造函数");
    }
    public NormalClass()
    {
    
    
        Debug.Log("我是普通类的实例构造函数");
    }
}
void Start()
{
    
    
    UtilityClass.MethodA(); // 我是静态类的静态构造函数
    NormalClass.MethodA(); // 我是普通类的静态构造函数
    UtilityClass.MethodA(); // 无
    NormalClass.MethodA(); // 无
    //UtilityClass u = new UtilityClass(); 错误,静态类无法实例化
    NormalClass n = new NormalClass(); // 我是普通类的实例构造函数
}

And if you instantiate a normal class alone:

void Start()
{
    
    
    NormalClass n = new NormalClass(); 
    // n.MethodA(); 错误,实例化对象无法访问静态方法
    // 先后输出两行:
    // 我是普通类的静态构造函数
    // 我是普通类的实例构造函数
}

inherit

Inheritance is to create a new class based on an existing class, retaining the attributes and methods of the original class, while adding new content.
 The existing class is called the base class or parent class, and the inherited class is called the derived class or subclass.

inheritance principle

In C#, only class, struct, and interface can be inherited, and class can inherit the only parent class and any number of other interface classes. Struct and interface can only inherit interface classes.

Insert image description here
Each level of inheritance can add some inheritable methods to the next level. There are also some members that cannot be inherited. Please see the access modifier written above for its relationship.

When accessing members in a class, they are accessed step by step upwards. If there are members of the subclass and the parent class with the same name, the subclass members are accessed first.

sealed modifier

The sealed modifier is used to prevent the class from being inherited.

sealed class Father{
    
    
}

class Son:Father{
    
      //错误,不可被继承
}

The purpose of sealed classes is to prevent some of the lowest-level classes from being inherited and to strengthen object-oriented standardization, structure and security.

Richter substitution principle

Wherever a base class can appear, a derived class can appear. From the perspective of inheritance, subclasses are expanded on the basis of the parent class and are naturally more comprehensive than the parent class. On the other hand, to follow the Liskov substitution principle, some methods defined in the subclass also need to be available in the parent class.

Let’s look at an example:

//基类
public class Mouse
{
    
    
    public void Dig()
    {
    
    
        Debug.Log("打洞");
    }
}
//派生类
public class MouseSon : Mouse
{
    
    
    public void Dig()
    {
    
    
        Debug.Log("不会打洞");
    }
}
static void main()
{
    
    
    Mouse m1 = new Mouse();
    m1.Dig(); // 打洞
    MouseSon m2 = new MouseSon();
    m2.Dig(); // 不会打洞,与父类方法重名时优先访问该类中的方法
}

The mouse's son cannot drill holes, which violates the Richter substitution principle. In this case, the subclass cannot be used while the parent class is available, and the subclass cannot replace the parent class.

Usually the Liskov substitution principle has two basic requirements:

  1. Subclass objects can be used instead of parent class objects
  2. When the parent class container contains subclass objects, the implementation is the same as 1
//基类
public class Mouse
{
    
    
    public void Dig()
    {
    
    
        Debug.Log("打洞");
    }
}
//派生类,变异老鼠儿子,可以飞
public class MouseSon : Mouse
{
    
    
	public void Fly()
	{
    
    
        Debug.Log("飞");
    }
}
static void main()
{
    
    
    Mouse m1 = new Mouse();
    m1.Dig(); // 打洞
    MouseSon m2 = new MouseSon();
    m2.Dig(); // 打洞
    m2.Fly(); // 飞
    Mouse m3 = new MouseSon();
    m3.Dig(); // 打洞
    // m3.Fly(); 老鼠爸爸不能飞
}

Constructor in inheritance

When a subclass inherits a parent class, if you want to define a constructor in the subclass, either the parent class does not define any constructor, or the parent class defines a parameterless constructor:

class Parent
{
    
    
	// 父类无构造方法或定义下列无参数构造方法
    //public Parent()
    //{
    
    
        //Debug.Log("我是Parent");
    //}
}
class Son : Parent
{
    
    
    public Son()
    {
    
    
        Debug.Log("我是Son");
    }
}

However, if a constructor with parameters is defined in the parent class and no constructor without parameters is defined, an error will be reported if the constructor defined in the subclass (whether with or without parameters) is defined. Unless the base keyword is used to define the corresponding parameters returned:

class Parent
{
    
    
    public Parent(int i)
    {
    
    
        Debug.Log("我是Parent");
    }
}
class Son : Parent
{
    
    
    public Son(int i) : base(i)
    {
    
    
        Debug.Log("我是Son");
    }
}

In general, the number of parameters in the constructor of the subclass and the constructor of the parent class must correspond. If the parent class constructor accepts at least one parameter, then the corresponding child class constructor needs to accept the parameters and call the parent class constructor.


Polymorphism

interface

When defining an interface, we need to use interfacekeywords to define it. The standard interface naming format requires an uppercase Irepresentation at the beginning interface. Interfaces can contain methods, properties, indexers, and events. It cannot contain any other members, such as constants, fields, domains, constructors, destructors, and static members.

interface IHuman
{
    
    
    // 接口中不允许任何显式实现
    void Name();
    void Age();
    public int Make {
    
     get; set; }
    public string this[int index]
    {
    
    
        get;
        set;
    }
}

When inheriting an interface, you also need to implement all methods of the interface. An interface can also inherit other interfaces. When an interface inherits multiple other interfaces, this interface is called a derived interface, and the other interfaces are called base interfaces. A class that inherits a derived interface not only needs to implement all methods in the derived interface, but also needs to implement all methods in the base interface:

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();
    }
}

The definition of interface is generally used for common behaviors. When multiple classes need to implement these behaviors, and the method of each class to implement the behavior needs to be rewritten, interfaces are very necessary.

Instantiation of interface

During the learning process, I originally thought that interfaces cannot be instantiated, but in fact many blogs on the Internet are wrong. Interfaces in C# can be instantiated!

Interfaces cannot be instantiated directly. Its members are implemented by any class or structure that implements the interface. (MSDN)

public class SampleClass : IControl, ISurface
{
    
    
    void IControl.Paint()
    {
    
    
        System.Console.WriteLine("IControl.Paint");
    }
    void ISurface.Paint()
    {
    
    
        System.Console.WriteLine("ISurface.Paint");
    }
}

SampleClass sample = new SampleClass();
IControl control = sample;
ISurface surface = sample;

// The following lines all call the same method.
//sample.Paint(); // Compiler error.
control.Paint();  // Calls IControl.Paint on SampleClass.
surface.Paint();  // Calls ISurface.Paint on SampleClass.

// Output:
// IControl.Paint
// ISurface.Paint

As shown above, when a class inherits an interface, we can implicitly convert instances of this class into instances of the changed interface. This interface instance contains objectthe four methods of the base class and the methods of its own interface. If we call the interface method, we will find that the method of the interface instance points to the method overridden by the class. The advantage of this method is that when a class inherits an interface that has a method with the same name, we can distinguish calls to methods with the same name by instantiating the interface.
Of course, you can also explicitly convert classes that do not implement this interface to this interface. Of course, this is of no use and an error will be reported at runtime.


Abstract classes and abstract methods

The definition keyword of abstract class and abstract method is abstractthat abstract method must be defined in abstract class, and abstract method must be public. However, abstract classes, like interfaces, cannot be instantiated, and all methods in the abstract class need to be implemented. The key word for overriding abstract methods is that overrideabstract classes cannot be sealedmodified because they are created for the purpose of inheritance.

abstract class Parent
{
    
    
   protected int age = 1; 
   public Parent() {
    
     }
   abstract public void callName();
   abstract public void callAge();
}
class Child : Parent
{
    
    
   public Child():base()
   {
    
    
   }
   public override void callName()
   {
    
    
      throw new NotImplementedException();
   }
   public override void callAge()
   {
    
    
      Debug.Log(age);
   }
}

After all, abstract classes are also classes. What is special is that although abstract classes cannot be instantiated, they can have some instantiation characteristics, such as properties or construction methods that do not need to be abstractmodified. Subclasses can also be used as if they inherited a normal class.
If an abstract class inherits an interface, it also needs to implement the methods in the interface, but the abstract class can implement it in the form of abstract methods:

    abstract class Parent:IHuman
    {
    
    
        protected int age = 1;
        public Parent() {
    
     }
        abstract public void callName();
        abstract public void callAge();
        public void Name()
        {
    
    
            throw new NotImplementedException();
        }
        abstract public void Age();
    }

Abstract classes can implement some specific methods and can also have specific attributes. Except for implementing abstract methods, there is no other difference from ordinary classes.

Similarities and Differences between Abstract Classes and Interfaces

Similar points: Both abstract classes and interfaces need to be inherited to implement, and neither can be instantiated directly. Subclasses that inherit them need to implement the abstract (interface) methods, and those that inherit the interface also need to implement the attributes and indexes. The difference between abstract classes and
so on: abstract classes cannot be instantiated, interfaces can be instantiated indirectly through type conversion of inherited classes, abstract classes can have specific methods, and interfaces can only define function headers. Many blogs on the Internet regard interfaces as a special class
. But in my opinion, an interface is an interface and a class is a class. These two are completely different things. Some blogs even say, "The difference between interfaces and abstract classes is that interfaces cannot inherit other classes, but abstract classes can." Interfaces are not classes in the first place. You can only inherit interfaces. How do you inherit classes?

virtual method

Use virtualkeywords to modify method, property, indexer, or event declarations so that they can be overridden in derived classes. ( virtualModifiers cannot be used for static properties)

    class Animal
    {
    
    
        public virtual void Eat()
        {
    
    
            Debug.Log("吃素");
        }
    }
    class Lion:Animal
    {
    
    
        public override void Eat()
        {
    
    
            Debug.Log("吃肉");
        }
    }
    class Sheep:Animal
    {
    
    
    }

The difference between virtual methods and abstract methods is that virtual methods can be used in any class, whether it is an ordinary class or an abstract class. Using virtual methods, we can flexibly decide whether we need to define a virtual method in the parent class, and whether the subclass needs to override this virtual method. These are all decided by ourselves and there will be no mandatory requirements. Just like the above example, if you need to override it, use overridethe overridden virtual method. If you don't need it, just inherit it directly. In addition, virtualthe keyword must declare the function body, which also means that it cannot be used in interfaces or abstract methods.

        Lion lion = new Lion();
        lion.Eat(); // 吃肉
        Sheep sheep = new Sheep();
        sheep.Eat(); // 吃素
        Animal Cow = new Animal();
        Cow.Eat(); // 吃素
        Animal Tiger = new Lion();
        Tiger.Eat(); // 吃肉

Method with the same name

In subclasses, you can use some methods with the same name, mainly in two situations:

  1. When overriding parent class method
  2. When the inherited class and interface have the same method name definition

Parent class method overridden by new

for example:

    class Animal
    {
    
    
        public void Eat()
        {
    
    
            Debug.Log("吃素");
        }
    }
    class Lion:Animal
    {
    
    
        public new void Eat() // 是否使用new关键字都会覆盖,使用new来指示这是我们有意覆盖父类方法
        {
    
    
            Debug.Log("吃肉");
        }
    }
    class Sheep:Animal
    {
    
    
    }

According to the inherited structure diagram above, methods will be searched level by level from the subclass to the parent class during runtime. If the method of the same name of the new subclass covers the method of the parent class, the method of the subclass will be executed directly. If the subclass is not defined, the method of the parent class is used:

        Lion lion = new Lion();
        lion.Eat(); // 吃肉
        Sheep sheep = new Sheep();
        sheep.Eat(); // 吃素
        Animal Cow = new Animal();
        Cow.Eat(); // 吃素
        Animal Tiger = new Lion();
        Tiger.Eat(); // 吃素

Inherited method with the same name

Now there is an interface as follows:

interface IEat
{
    
    
    void Eat();
}

Now we have a sheepclass that inherits both Animaland IEatdefined as follows:

    class Wolf : Animal, IEat
    {
    
    
        public void Eat()
        {
    
    
            Debug.Log("吃肉");
        }
    }
    void Start()
    {
    
    
        Wolf wolf = new Wolf();
        wolf.Eat(); // 吃肉
        IEat eat = wolf;
        eat.Eat(); // 吃肉
    }

We found that in the above case, the Eat() method was rewritten at the same time. The methods Wolfin the class Eatoverwrote the methods Animalin the class Eat, and at the same time, Eatthe methods of the interface were also rewritten. When methods with the same name appear, these methods will be overridden at the same time. And if we specify the exact method name:

    class Wolf : Animal, IEat
    {
    
    
        public void Eat()
        {
    
    
            Debug.Log("吃肉");
        }
        void IEat.Eat()
        {
    
    
            base.Eat();
        }
    }
    void Start()
    {
    
    
        Wolf wolf = new Wolf();
        wolf.Eat(); // 吃肉
        IEat eat = wolf;
        eat.Eat(); // 吃素
    }

Different methods can be defined precisely by specifying the interface name.


Runtime polymorphism

Careful readers may have discovered: when we override overridevirtual methods:

        Animal Tiger = new Lion();
        Tiger.Eat(); // 吃肉

And when we override the original method:

        Animal Tiger = new Lion();
        Tiger.Eat(); // 吃素

At runtime, in addition to looking up the method (property) to be executed step by step, it will also check whether it has rewritten virtualkeywords. When using a parent class container to load subclass objects, if the virtual method has been overridden by the subclass , then the overridden method will be executed. For the execution of virtual methods, only the first found overridermethod will be executed. Talk is cheap. Take a look at the following example:

    class Animal
    {
    
    
        public virtual void Eat()
        {
    
    
            Debug.Log("吃素");
        }
    }
    class Lion:Animal
    {
    
    
        public override void Eat()
        {
    
    
            Debug.Log("吃肉");
        }
    }
    class Tiger : Lion
    {
    
    
        public new void Eat()
        {
    
    
            Debug.Log("吃兔子");
        }
    }

    void Start()
    {
    
    
        Animal tiger = new Tiger();
        tiger.Eat(); // 吃肉
    }

In the above example, Eat()the execution is to eat meat instead of eating rabbits. If you check it level by level, the first one found should output eating rabbits. However, since the base class Aniamlis a virtual method, the first overridden method Eat()found level by level is executed , so the output is meaty.overrideEat()

    class Tiger : Lion
    {
    
    
        public override void Eat() // 如果使用override再次重写
        {
    
    
            Debug.Log("吃兔子");
        }
    }

    void Start()
    {
    
    
        Animal tiger = new Tiger();
        tiger.Eat(); // 吃兔子
        Lion tiger = new Tiger();
        tiger.Eat(); // 吃兔子
    }

If overrideoverriding is used again, eating rabbit will be displayed. What is interesting is that although the method in the Lion class is not virtual, we still override Animalthe virtual method of the base class.

    class Lion:Animal
    {
    
    
        public sealed override void Eat()
        {
    
    
            Debug.Log("吃肉");
        }
    }
    class Tiger : Lion
    {
    
    
        public override void Eat() // 重写报错,Eat方法已经被密封了
        {
    
    
            Debug.Log("吃兔子");
        }
    }

If we don't want the class to override the method after Tigerinheriting , we should use keywords to seal the base class method to ensure that it will not be overridden after inheritance .LionEat()sealedLion


Compile-time polymorphism

In the program, we also need to use some methods with the same name but different parameters. This is to facilitate the use of the same method in different situations. We call it overloading ( overload), for example:

    void Start()
    {
    
    
        HumanEat(吃点啥呢 ?);
    }
    string HumanEat(Animal meat) {
    
     return ""; }
    int HumanEat(Animal meat, Vegetable fruit) {
    
     return 1; }
    void HumanEat(Vegetable vegetable) {
    
     }

The same people eat the same food, but different people eat different things. Vegetarians only eat vegetarian food, most people eat both meat and vegetables, and carnivores only eat meat. Sometimes we even need to distinguish the return value of a function. When we need to call the HumanEat method and need to make specific distinctions, overloading is the best choice, so that the same function can be called flexibly, instead of putting all the situations in one function and reloading it every time there is an additional situation. Writing the original function or defining n number of methods makes it difficult to just remember the function name.


Summary: There are a lot of concepts in this section, but every knowledge point has its own meaning. For example, if we want a tool class that can be called directly from the namespace without instantiation, we should use a static class. Using a static constructor of a static class guarantees a unique call at initialization time. Consider inheritance patterns when using different parent classes. When using polymorphism, consider when and what kind of polymorphism should be used? If we want a base class that only has a structure definition but no concrete implementation and is not instantiated, we need an abstract class. When we need flexible inheritable function overriding, we need an interface. When we need to implement the same method in multiple situations, we need to overload it. If you want polymorphism, virtual methods are better than overriding methods. All object-oriented features should be carefully considered during actual use, carefully considered during design, and the program designed based on experience.

Guess you like

Origin blog.csdn.net/milu_ELK/article/details/132143125