返回文章列表文章索引阅读全文
STARBLOG / 阅读手记
C#

C# Learning (1): The Concept and Use of Delegates

Preface The C# learning series is compiled from my previous notes for review, and I'm posting articles along the way as a record. First, here's a quoted introduction: Delegates in C# are similar to function pointers in C or C++. A delegate is a reference type variable that holds a reference to a method. The reference can be changed at runtime. Delegates are especially used to implement events and callback methods. All delegates derive from the System.Delegate class. Based on using other

曦远 (DealiAxy)约 18 分钟
目录7 个章节

Preface

The C# learning series is compiled from my previous notes for review, and I'm posting articles along the way as a record.

First, here's a quoted introduction:

Delegates in C# are similar to function pointers in C or C++. A delegate is a reference type variable that holds a reference to a method. The reference can be changed at runtime. Delegates are especially used to implement events and callback methods. All delegates derive from the System.Delegate class.

Based on experience with other languages, in Python and JS, functions (methods) are also a type: they can be assigned to variables and then invoked through those variables. It's pretty much the same idea in C#, except that a C# delegate can hold multiple functions (methods), and when the delegate is invoked, multiple methods are called at the same time—this is called a multicast delegate. I haven't looked into the specific implementation details of delegates yet; I'll go deeper in the upcoming studies~

Code Example

using System;

namespace DelegateAndEvent
{
    class Program
    {
        delegate void voidDelegate();

        delegate int intDelegate();

        static void Main(string[] args)
        {
            // 定义一个多播委托
            voidDelegate d1 = T1;
            d1 += T2;
            d1 += T3;
            d1 += T4;

            // 执行委托
            // 会同时执行t1,t2,t3,t4四个方法
            d1();
        }

        static void T1()
        {
            Console.WriteLine("1");
        }

        static void T2()
        {
            Console.WriteLine("2");
        }

        static void T3()
        {
            Console.WriteLine("3");
        }

        static void T4()
        {
            Console.WriteLine("4");
        }

        static int Q1()
        {
            return 1;
        }

        static int Q2()
        {
            return 2;
        }

        static int Q3()
        {
            return 3;
        }
    }
}

A Bit of Understanding

To understand delegates, you first need to understand C#'s type system. The CLR supports two fundamental types: value types and reference types. The diagram below gives a fairly clear picture of C#'s type system. Most of you have only studied Java and may not have gone deep into C or C++. The reason C# can maintain high performance in various scenarios (such as computationally intensive ones like game engines) is largely due to its excellent type system design.

Introduction to Value Types and Reference Types

Value types (Value Type): instances of value types are typically allocated on the thread's stack, and they do not contain any pointer to the instance data, because the variable itself contains its instance data. MSDN defines them as: value types directly contain their data; instances of value types are either on the stack or inlined in a structure. As we can see from the diagram above, value types mainly include simple types, struct types, enum types, and so on. When a variable is declared as one of the following types: int, char, float, long, bool, double, struct, enum, short, byte, decimal, sbyte, uint, ulong, ushort, etc., that variable is a value type.

Reference types (Reference Type): instances of reference types are allocated on the managed heap, and the variable holds a memory reference to the instance data. MSDN defines them as: reference types store a reference to the memory address of a value, located on the heap. As we can see from the diagram above, a reference type can be a self-describing type, a pointer type, or an interface type. Self-describing types are further subdivided into arrays and class types. Class types can be user-defined classes, boxed value types, and delegates. When a variable is declared as one of the following types: class, interface, delegate, object, string, or other custom reference types, that variable is a reference type.

Memory Allocation

Where data is allocated in memory depends on the variable's data type. As described above, value types are typically allocated on the thread's stack, while reference types are typically allocated on the managed heap, with their reclamation controlled by the GC. As shown below:

We know that every variable or program has its stack, and different variables cannot share the same stack address. Therefore myStruct and myStruct2 must occupy different stack addresses; even after variable assignment, the actual memory is still allocated at different addresses. If we then change the myStruct2 variable, it clearly will not affect myStruct's data. From the diagram we can also readily see that myStruct contains its instance data on the stack, whereas myClass on the stack only holds a reference address to its instance data, with the actual data stored on the managed heap. Therefore, it is possible for different variables to hold a data reference to the same address. When data is passed from one reference-type variable to another reference-type variable of the same type, what is passed is the reference address rather than the actual data, so a change to one variable will affect the value of the other. From the analysis above, we can clearly understand this simple principle: the difference in how value types and reference types are allocated in memory is the fundamental reason their applications differ. From this we can easily explain why, when passing parameters, passing by value does not change the formal parameter's value, whereas passing by address does change the formal parameter's value—this is exactly why.

A more detailed description of where memory is allocated is as follows:

  • When a value-type variable is used as a local variable, the instance is created on the stack; if a value-type variable is a member variable of a type, it becomes part of that type's instance data and is stored on the managed heap together with the type's other fields. We will explain this in detail in the upcoming nested-structure section.

  • Reference-type variable data is stored on the managed heap, but there is a distinction based on instance size, as follows: if the instance size is less than 85000 bytes, the instance is created on the GC heap; when the instance size is greater than or equal to 85000 bytes, the instance is created on the LOH (Large Object Heap).

Afterword

OK, that's about it for now. I'll continue studying tomorrow~

Welcome to Discuss

For questions, please leave a message on the WeChat official account backend. I will reply to every message~

_EOF_

C# Learning (1): The Concept and Use of Delegates

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…