当前位置:网站首页>Ref in C has been released. Maybe you don't know it

Ref in C has been released. Maybe you don't know it

2020-11-10 14:23:19 osc_eqcu0796

One : background

1. Tell a story

I've been looking at netcore Source code to see , Found that a lot of code in the framework is ref To be decorated with , I went to , This is what I know ref Do you ? take Span Come on , The code is as follows :


    public readonly ref struct Span<T>
    {
        public ref T GetPinnableReference()
        {
            ref T result = ref Unsafe.AsRef<T>(null);
            if (_length != 0)
            {
                result = ref _pointer.Value;
            }
            return ref result;
        }

        public ref T this[int index]
        {
            get
            {
                return ref Unsafe.Add(ref _pointer.Value, index);
            }
        }             
    }

Is there... Everywhere ref, stay struct There are , stay local variable Also have , stay Method signature Also have , stay Method call Also have , stay attribute There are also , stay return It's about Also have , It's just everything , too ???????? La , Let's talk about this wonderful work ref.

Two :ref Code analysis in each scenario

1. motivation

I don't know if you found out , stay C# 7.0 after , The language team is really paying more attention to performance than ever before , There are also various types and underlying support for this , for instance Span, Memory,ValueTask, What's more, this article will introduce ref.

In our traditional cognition ref Is used on method parameters , For giving Value type Do reference to pass value , We need to modify the business situation many times , The second is to avoid value types copy The resulting performance overhead , I don't know which God's brain is opening up , take ref Apply it all over the code you know , The ultimate goal is to improve performance as much as possible .

2. ref struct analysis

Have been educated since childhood The value type is assigned on the stack , The reference type is on the heap , There's something wrong with that , Because value types can also be assigned on the heap , For example, the following code Location.


    public class Program
    {
        public static void Main(string[] args)
        {
            var person = new Person() { Name = " Zhang San ", Location = new Point() { X = 10, Y = 20 } };

            Console.ReadLine();
        }
    }

    public class Person
    {
        public string Name { getset; }

        public Point Location { getset; }  // Distributed on the heap 
    }

    public struct Point
    {
        public int X { getset; }
        public int Y { getset; }
    }

In fact, this is also a lot of novice friends learning value type doubts , It can be used windbg Go to the escrow heap and look for it Person Ask , The following code :


0:000> !dumpheap -type Person
         Address               MT     Size
0000010e368aadb8 00007ffaf50c2340       32     

0:000> !do 0000010e368aadb8
Name:        ConsoleApp2.Person
MethodTable: 00007ffaf50c2340
EEClass:     00007ffaf50bc5e8
Size:        32(0x20) bytes
File:        E:\net5\ConsoleApp1\ConsoleApp2\bin\Debug\netcoreapp3.1\ConsoleApp2.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffaf5081e18  4000001        8        System.String  0 instance 0000010e368aad98 <Name>k__BackingField
00007ffaf50c22b0  4000002       10    ConsoleApp2.Point  1 instance 0000010e368aadc8 <Location>k__BackingField

0:000> dp 0000010e368aadc8
0000010e`368aadc8  00000014`0000000a 00000000`00000000

The last line of code above 00000014`0000000a Medium 14 and a Namely y and x Value , It's safe in the pile , If you don't believe it, look at it gc 0 The scope of the generation heap .


0:000> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x0000010E368A1030
generation 1 starts at 0x0000010E368A1018
generation 2 starts at 0x0000010E368A1000
ephemeral segment allocation context: none
         segment             begin         allocated              size
0000010E368A0000  0000010E368A1000  0000010E368B55F8  0x145f8(83448)

You can see from the last line that , Just now  0000010e368aadc8 It is indeed. 0 Generation reactor 0x0000010E368A1030 - 0000010E368B55F8 Within the scope of .

The next question is whether we can give struct Make a limit , Just like generic constraints , forbid struct Distributed on the heap , Is there any way ? The way is to add a ref Limit it , Here's the picture :

You can see from the error prompt that , Deliberately let struct Operations assigned to the heap are strictly prohibited , To think about it, the compiler can only class person Change to ref struct person, That's the beginning of the article Span   and  this[int index] such , The motive is conceivable , It's all about performance .

3. ref method analysis

Pass the reference address to the parameter of the method , I think a lot of friends are already familiar with the road , Like the following :


        public static int GetNum(ref int i)
        {
            return i;
        }

Now you can try to get out of the box , Since we can still Refer to the address , Can we throw out the method Refer to the address Well ? It would be interesting if it could be done , I can refer to some data in the collection and return it , You can also modify these return values outside the method , After all, they're all referring to addresses , As shown in the following code :


    public class Program
    {
        public static void Main(string[] args)
        {
            var nums = new int[3] { 102030 };

            ref int num = ref GetNum(nums);

            num = 50;

            Console.WriteLine($"nums= {string.Join(",",nums)}");

            Console.ReadLine();
        }

        public static ref int GetNum(int[] nums)
        {
            return ref nums[2];
        }
    }

You can see , The last value of the array has been created by 30 -> 50 了 , Some friends may be surprised , How does this work , You don't have to think about it. It's just a reference address floating around , Don't believe it , have a look IL Code .


.method public hidebysig static 
 int32& GetNums (
  int32[] nums
 ) cil managed 
{
 // Method begins at RVA 0x209c
 // Code size 13 (0xd)
 .maxstack 2
 .locals init (
  [0] int32&
 )

 // {
 IL_0000nop
 // return ref nums[2];
 IL_0001ldarg.0
 IL_0002ldc.i4.2
 IL_0003ldelema [System.Runtime]System.Int32
 IL_0008stloc.0
 // (no C# code)
 IL_0009br.s IL_000b

 IL_000bldloc.0
 IL_000cret// end of method Program::GetNums

.method public hidebysig static 
 void Main (
  string[] args
 ) cil managed 
{
 IL_0013: ldloc.0
 IL_0014: call int32& ConsoleApp2.Program::GetNums(int32[])
 IL_0019: stloc.1
 IL_001a: ldloc.1
 IL_001b: ldc.i4.s 50
 IL_003e: pop
 IL_003f: ret
} // end of method Program::Main


You can see , Everywhere, & Value operators , More intuitive words to use windbg to glance at .


0:000> !clrstack -a
OS Thread Id: 0x7040 (0)
000000D4E777E760 00007FFAF1C5108F ConsoleApp2.Program.Main(System.String[]) [E:\net5\ConsoleApp1\ConsoleApp2\Program.cs @ 28]
    PARAMETERS:
        args (0x000000D4E777E7F0) = 0x00000218c9ae9e60
    LOCALS:
        0x000000D4E777E7C8 = 0x00000218c9aeadd8
        0x000000D4E777E7C0 = 0x00000218c9aeadf0

0:000> dp 0x00000218c9aeadf0
00000218`c9aeadf0  00000000`00000032 00000000`00000000

In the code above 0x00000218c9aeadf0 Namely num Reference address of , Keep using dp Take a look at the value on this address as 16 It's binary 32, That's decimal 50 Ha .

3、 ... and : summary

in general ,netcore It was in the early days that Cloud computing and virtualization The age was born , Gene and mission make it have to be optimized, optimized and optimized again , The smallest ant is meat , The last is C# Dafa ????????

版权声明
本文为[osc_eqcu0796]所创,转载请带上原文链接,感谢