.NET C# Basics: Method Modifiers - Stacking buffs on methods

Article purpose

This article is aimed at learners who have a certain basic knowledge of .NET C#, and introduces the meaning, use and precautions of method modifiers in C#.

1. Reading Basics

  • Understand C# basic syntax (such as method declaration)
  • Understand the basic concepts of OOP (such as polymorphism)

2. Concept: what is a method modifier

In C#, a method is usually declared as follows

[访问修饰符] [方法修饰符] [返回类型] 方法名(参数列表)

For example, a method declared as follows:

public virtual async Task HelloAsync();

The virtualand asyncare the method modifiers, which indicate the characteristics of the method to the compiler, so that the compiler can treat the method specially. For example, the method modifier here indicates that the method is a virtual method ( ) that can be overridden by subclasses, virtual)and is an asynchronous method ( async).

This article focuses on [method modifiers]. In C#, there are the following method modifiers:

  • abstract
  • virtual
  • override
  • sealed
  • new
  • async
  • static
  • readonly
  • extern
  • partial
  • unsafe

There is mutual exclusion between most method modifiers, that is, one modifier cannot be used after another modifier is used. You don't need to deliberately remember the mutual exclusion relationship, you just need to understand the meaning of each modifier.

3. Starting from an example: how to use method modifiers

According to different classification methods, the above method modifiers can be classified into several groups. Here we classify them according to the nature of the modifiers:

implement polymorphism for encapsulation change nature characteristic indication
virtual sealed static async
override new readonly
abstract extern
partial
unsafe

The following introduces each modifier one by one according to the above organization

3.1. Implement polymorphism

3.1.1 virtual与override

(1) use

The virtual modifier is mainly used to mark a method that can be overridden by subclasses, and its main usage methods are as follows:

class Base
{
    public virtual void Hello()
    {
        Console.WriteLine("Hello, I am Base");
    }
}

To override a method modified by virtual, you need to declare a method with the same function signature in the subclass and use the override modifier:

class Dervied : Base
{
    public override void Hello()
    {
        Console.WriteLine("Hello, I am Dervied");
    }
}

Here is an example call:

Base b = new Base();
Dervied d = new Dervied();

b.Hello(); // Hello, I am Base
d.Hello(); // Hello, I am Dervied

b = d;
b.Hello(); // Hello, I am Dervied

The third output in the above code will output "Hello, I am Dervied". This is because although the variable b is a reference of the Base type, it actually points to an object of the Dervied type. Since Dervied rewrites the Hello method of its base class Base, when the Hello method is called through the variable b, according to the polymorphism At this time, the Hello method defined in Dervied is actually called.

(2) Special instructions

Virtual and override are used to modify methods, but since the essence of attributes in C# is also methods, they can also be applied to modified attributes to achieve polymorphism of attributes, as follows:

class Base
{
    public virtual int Value
    {
        get
        {
            return 0;
        }
    }
}
class Dervied : Base
{
    public override int Value
    {
        get
        {
            return 1;
        }
    }
}

Base b = new Dervied();
Console.WriteLine(b.Value); // 输出是1,因为此时实际调用的是Dervied中定义的Value属性

3.1.2 abstract

(1) use

The abstract modifier is also used to mark a method that can be overridden by subclasses, but its use has strict restrictions:

  1. abstract can only modify abstract methods, and abstract methods must be modified with abstract
  2. Subclasses must override methods modified by abstract

The so-called abstract method is a method that has only a method declaration but no method body, and is modified by abstract. This method can only be defined in an abstract class (abstract class), as follows:

abstract class Base
{
    public abstract void Hello();
}

Hello is an abstract method, which only defines the method signature but not the method body, so its actual performance is determined by the inherited class, so the inherited class must rewrite the abstract method in the parent class (unless the inherited class is still an abstract class, it is not necessary rewriting), rewriting the abstract method is consistent with rewriting the virtual method:

class Dervied : Base
{
    public override void Hello()
    {
        // do some thing
    }
}

(2) Special instructions

Obviously the use of the abstract modifier is quite fixed, although it seems that C# can completely default a method without a method body to an abstract method to avoid the use of the abstract modifier. The reason for retaining this design may be to enhance semantics.

Virtual can be used in almost all places where abstract is used to modify methods. The main feature of abstract is that it will force subclasses to rewrite the methods modified by it, which is a coding specification agreement. In a sense, abstract is actually more inclined to simulate interface method declarations. as follows:

abstract class IFlayable
{
    public abstract void Fly();
}

The above declaration is actually similar to the following interface declaration:

interface IFlyable
{
    void Fly();
}

3.2. For encapsulation

3.2.1 sealed

(1) use

When used to modify methods, the meaning of sealed is: the modified method cannot be overridden by subclasses. Since only methods declared as virtual or abstract in the parent class can be overridden by its subclasses, and obviously you cannot use the sealed modifier with virtual or abstract, only methods that are overridden in its subclasses can be overridden. Has the potential to continue to be overridden by subclasses of subclasses. As the following code:

class A
{
    public virtual void Hello()
    {
        Console.WriteLine("I am A");
    }
}
class B : A
{
    public override void Hello()
    {
        Console.WriteLine("I am B");
    }
}
class C : B
{
    // 此Hello方法将再次重写基类的Hello方法
    public override void Hello()
    {
        Console.WriteLine("I am C");
    }
}

The virtual method Hello is defined in base class A. Although type B has rewritten the Hello method in base class A, and type C inherits from type B, you can still rewrite it in type C and define it in base class A The Hello method. Sometimes based on some encapsulation requirements, you may want to avoid the above situation, so you need to use the sealed modifier. as follows:

class A
{
    public virtual void Hello()
    {
        Console.WriteLine("I am A");
    }
}
class B : A
{
    public sealed override void Hello()
    {
        Console.WriteLine("I am B");
    }
}
class C : B
{
    // 此方法无法通过编译,因为类型B中将Hello方法设为了sealed方法
    public override void Hello()
    {
        Console.WriteLine("I am C");
    }
}

In short, the sealed modifier is to make the method that can be overridden return to the non-overridable state in the subclass

3.2.2 new

(1) use

Sometimes a method with the same signature as the base class method may be defined in the subclass, but the base class does not mark the method as rewritable with virtual or abstract, as follows:

class Base
{
    public void Hello()
    {
        Console.WriteLine("Hello, I am Base");
    }
}
class Dervied : Base
{
    public void Hello()
    {
        Console.WriteLine("Hello, I am Dervied");
    }
}

The above code can be compiled, and the Hello method defined in Dervied will hide the Hello method defined in Base, but it will receive a warning from the compiler. At this time, you can use the new keyword to force the subclass to override the method with the same signature in the base class and avoid compiler warnings.

class Base
{
    public void Hello()
    {
        Console.WriteLine("Hello, I am Base");
    }
}
class Dervied : Base
{
    // 通过new修饰后,将不会有编译器警告
    public new void Hello()
    {
        Console.WriteLine("Hello, I am Dervied");
    }
}

However, this explicit overriding behavior also does not provide polymorphism, which means that the following code execution results:

Base b = new Base();
Dervied d = new Dervied();

b.Hello(); // Hello, I am Base
d.Hello(); // Hello, I am Dervied

b = d;
b.Hello(); // Hello, I am Base

When calling Hello for the third time, although b already points to a Dervied object at this time, when the Hello method is called, the Hello method defined in Base is still called. In other words, the Hello method is not polymorphic. In fact, the meaning of the new modifier is: the modified method has no relationship with members of similar signatures of the base class.

(2) Special instructions

Unless there is a compelling reason, when the subclass method signature conflicts with the parent class, priority should be given to modifying the method name to avoid conflicts instead of using the new modifier.

In addition to being used for methods, new can also be used for properties, fields and even events:

class Base
{
    public event Action? Action;
    public int Field;
    public int Property { get; set; }
}
class Dervied : Base
{
    public new event Action? Action;
    public new int Field;
    public new int Property { get; set; }
}

Remember, the actual meaning of the new modifier is: the modified method has no relationship with the members of the similar signature in the base class.

3.3. Change nature

3.3.1 static

(1) use

By default, the method declared in the class is an instance method, and its call needs to be called through an instance of the class, as follows:

class Printer
{
    public void Hello()
    {
        Console.WriteLine("Hello");
    }
}

Printer p = new Printer();
p.Hello(); // 通过Base类的实例来调用Hello方法

In most cases, this behavior is reasonable, because the methods of a class often involve accessing and modifying its instance fields. Sometimes however a method may not need to access any instance fields, for example, define a Math class that has an Add method for addition:

class Math
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

To use the Add method of the Math class, you need to call it as follows:

Math math = new Math();
int n = math.Add(1, 2);

However, this process is a bit cumbersome. The Add method itself does not depend on any instance field attributes in the Math class, and can run completely independently, and creating objects requires additional space and time. To avoid this meaningless behavior, consider bypassing instantiation and calling the Add method directly. At this point the static modifier can be used. The static modifier indicates that a method will not access instance fields in the class, and can be called directly using the class itself without instantiation, as follows:

class Math
{
    public static int Add(int a, int b)
    {
        return a + b;
    }
}

To use the Add method of the Math class, just call it like this:

int n = Math.Add(1, 2);

(2) Special instructions

A static method can be thought of as a function managed by a class.

Similarly, the static modifier can also be used to modify fields, properties and events:

class Foo
{
    public static Action? StaticEvent;
    public static int StaticField;
    public static int StaticProperty { get; set; }
}

// 直接通过类名调用
Foo.StaticEvent;
Foo.StaticField;
Foo.StaticProperty;

For static classes (static class), all members need to use static decoration.

3.4. Marker properties

3.4.1 async

(1) use

The async modifier is used to indicate that a method is an asynchronous method and needs to be used in conjunction with the await keyword in the method body. The concept of asynchronous methods is not elaborated here due to space, but its use is only explained here. Examples are as follows:

class Printer
{
    public async void HelloAsync()
    {
        await Task.Delay(1000);
        Console.WriteLine("Hello");
    }
}

It should be noted that the function of async is only to mark the method as an asynchronous method, not to indicate that the method should be called asynchronously. That is to say, in the following example, although Wait1 is marked by aysnc, the actual performance of Wait1 and Wait2 is the same, both It is a synchronous method, which will block the calling thread for 1 second:

class Foo
{
    public async void Wait()
    {
        Thread.Sleep(1000);
    }
    public void Wait()
    {
        Thread.Sleep(1000);
    }
}

Foo foo = new Foo();
foo.Hello1(); // 阻塞1秒
foo.Hello2(); // 阻塞1秒

To really play the role of async, it needs to cooperate with TAP (Task-based Asynchronous Pattern) asynchronous design pattern

(2) Special instructions

As a coding specification, the method name modified by async should end with Async, such as:

async void HelloAsync();

3.4.2 extern

(1) use

extern indicates that the method is implemented externally, and is usually used in conjunction with P/Invoke to call APIs written in other languages. For example, the declaration in the following method indicates that the method actually needs to call the Add method in the math library written in C language:

[DllImport("math.dll")]
private static extern int Add(int a, int b);

Note that in addition to being modified by extern, the above methods also need to be modified by static. This is understandable, because the method called from the external library will obviously not be an instance method for this class (both design logic and implementation logic make no sense).

(2) Precautions

The called APIs written in other languages ​​need to follow certain coding standards, so not all functions can be simply called as above. Considering the length and focus of the article, I will not repeat them here.

3.4.3 partial

(1) use

Before starting to introduce the partial method, you need to introduce the partial class first, because this partial method needs to be used in conjunction with the partial class. Simply put, a partial class means that a class can define class members in multiple places, which are merged by the compiler at compile time. For example, the following partial class declaration:

partial class Printer
{
    public void Hello()
    {
        Console.WriteLine("Hello");
    }
}
partial class Printer
{
    public void World()
    {
        Console.WriteLine("World");
    }
}

At compile time, the compiler merges the same type implementations of various parts, so the actual effect is equivalent to the following declaration:

class Printer
{
    public void Hello()
    {
        Console.WriteLine("Hello");
    }
    public void World()
    {
        Console.WriteLine("World");
    }
}

Although it seems that the partial class seems to make things troublesome, in fact, the partial class has many practical functions, such as merging user code and generated code. One application scenario is to merge the code automatically generated by WPF or WinForm window designer with Code written by the user. In addition, partial classes can also facilitate the collaborative development of classes. For more information about distribution classes, please refer to the official documentation: Partial Classes and Methods

The above is the basic usage knowledge of partial classes. The method modified by partial used in conjunction with the partial class is introduced below, which is called a partial method. For example, the following statement:

class Printer
{
    public void Hello()
    {
        Console.WriteLine("Hello");
    }
}

This can be modified to the following declarative form using partial classes and partial methods:

partial class Printer
{
    public partial void Hello();
}
partial class Printer
{
    public partial void Hello()
    {
        Console.WriteLine("Hello");
    }
}

Both are the same in effect and substance. You may wonder what is the meaning of this behavior, after all, it does not seem to bring any convenience, and it will write an extra method declaration. In fact, sometimes the implementation of the method may be generated by the code generator. At this time, the partial method is needed to help merge the method declaration defined by the user with the method implementation completed by the code generator.

(2) Precautions

If the partial method satisfies the following conditions, it is not necessary to provide code implementation.

  • Without any access modifiers (including the default private)
  • The return value is void
  • There are no output parameters (that is, parameters modified by out)
  • Without any of the following modifiers: virtual, override, sealed, neworextern

In fact, at compile time, the compiler will delete calls to partial methods that meet the above conditions and are not implemented.

3.4.4 readonly

(1) use

Unlike other modifiers, readonly can only be used to modify the method declaration of the structure, which means: the method body will not modify the instance fields of the structure. An example declaration is as follows:

struct Point
{
    public float X;
    public float Y;

    public readonly void Print()
    {
        Console.WriteLine(X + "," + Y);
    }
}

The above readonly modifier indicates that the Print method will not modify the instance fields X and Y, and only access behavior exists in the method. If you try to modify an instance field in a readonly method, it will result in a compilation error.

(2) Special instructions

In fact, with ref, readonly can also be used to modify class methods, but at this time readonly has completely different semantics, for example:

class Grid
{
    private Point _origin = new Point();

    public ref readonly Point GetOrigin()
    {
        return ref _origin;
    }
}

The actual meaning of readonly in the above statement is: the returned ref reference is an unmodifiable read-only reference. That is to say, the following code cannot be compiled:

Grid grid = new Grid();
ref Point p = ref grid.GetPoint(); // 错误
p.X = 1;

You can ensure that the return value of a read-only reference will not be modified by adding a readonly declaration to the ref variable:

ref readonly Point p = ref grid.GetPoint();

3.4.5 unsafe

(1) use

Unsafe actually indicates that the method can run unsafe code, which is a method-level declaration of the unsafe keyword. A simple unsafe method is as follows:

class Math
{
    public static unsafe void Increase(int* value)
    {
        *value += 1;
    }
}

The Increase method of the Math class accepts an int pointer and adds 1 to the pointed value. This method involves pointer operations and needs to accept a parameter of pointer type, so the method needs to be marked with unsafe. Unsafe method calls are similar to general method calls:

int n = 10;
unsafe
{
    Math.Increase(&n);
}
Console.WriteLine(n); // 11

Please note that unsafe does not have to be a static method, the method is declared as static just for the convenience of calling. In addition, the need to use the unsafe block here is not because Increase is an unsafe method, but because the address of the variable n needs to be obtained and passed to the method using the address character &.

(2) Special instructions

Compiling unsafe code requires specifying AllowUnsafeBlockscompiler options

おすすめ

転載: blog.csdn.net/sd2208464/article/details/129751666