Preface
A few days ago, I saw a group member writing a basic framework in which a scene where multiple synonyms can be added to the same word. At that time, the group members' design was a dictionary-like design, and they directly added k-v operations. After seeing it, I thought that using params in c# can implement a key more elegantly and add a collection operation, which looks more elegant. During this period, group members also talked about the difference between params and arrays. Let me talk about this article in a rough way.
Example
params is a keyword in C#. In Chinese, it is called variable parameters. The variable here does not mean that the type is variable, but refers to variable numbers. This is a basic keyword in C#. I believe everyone has a certain understanding. Today, let’s take a look at the variable parameters params in C#. First, let’s take a look at the simple customization and define any method
static void ParamtesDemo(string className, params string[] names) { ($"{className}The students have:{(",", names)}"); }
How many things to pay attention to when defining variable parameter types
- params are modified before the parameter and the parameter type must be a one-dimensional array type
- Params modified parameters can not be passed by default
- Params parameters cannot be modified with ref or out and cannot be manually given to default values.
It's easier to call, as shown below
ParamtesDemo("Little Class 4", "jordan", "kobe", "james", "curry"); // If the value is not passed, there will be no error// ParamtesDemo("Little Class 4");
From the above example, we can see that the biggest advantage of using variable parameters is that you can pass a collection type with uncertain numbers and do not need to declare a separate type to wrap it. This scenario is particularly suitable for passing in uncertain parameters, such as the ones we often useIt is the variable parameter type used.
Explore the essence
Through the traversality of params we learned above, it is the best scenario for using variable parameters when the number of set parameters is uncertain. It looks magical and convenient. What is the essence? The poster didn't care about this issue before, until he looked at it a few days ago with curiosity. Without further ado, we will take advantage of itILSpy
The tool looks at the source code after decompilation
[CompilerGenerated] internal class Program { private static void <Main>$(string[] args) { //Declare an array ParamtesDemo("Little Class 4", new string[4] { "jordan", "kobe", "james", "curry" }); (); //The params keyword is no longer a single array static void ParamtesDemo(string className, string[] names) { (className + "The students have:" + (",", names)); } } }
passILSpy
We can see that params is a syntax sugar in the decompiled source code, which actually increases programming efficiency. In essence, when compiling, it will be replaced by the specific declared array type and will not participate in the runtime. At this time, if you suspect that there is a problem with the decompiled code, you can directly passILSpy
Look at the generated IL code. Since the IL code is relatively long, let’s first look at the Main method.
// Methods .method private hidebysig static void '<Main>$' ( string[] args ) cil managed { // Method begins at RVA 0x2092 // Header size: 1 // Code size: 57 (0x39) .maxstack 8 .entrypoint // ParamtesDemo("Small Class 4", new string[4] { "jordan", "kobe", "james", "curry" }); IL_0000: ldstr "Little Class 4" IL_0005: ldc.i4.4 //It can be seen through newarr that an array type is indeed declared IL_0006: newarr [] IL_000b: dup IL_000c: ldc.i4.0 IL_000d: ldstr "jordan" IL_0012: IL_0013: dup IL_0014: ldc.i4.1 IL_0015: ldstr "kobe" IL_001a: IL_001b: dup IL_001c: ldc.i4.2 IL_001d: ldstr "james" IL_0022: IL_0023: dup IL_0024: ldc.i4.3 IL_0025: ldstr "curry" IL_002a: // This place calls ParamtesDemo, the second parameter is indeed an array type IL_002b: call void Program::'<<Main>$>g__ParamtesDemo|0_0'(string, string[]) // (); IL_0030: nop IL_0031: call valuetype [] []::ReadKey() IL_0036: pop // } IL_0037: nop IL_0038: ret } // end of method Program::'<Main>$'
Through the IL code above, we can see that it is indeed a syntactic sugar. After compiling, everything returns to dust and soil, it is still an array type, and the type is the same as the array type modified by params. Next, let's take a look at what the IL code of ParamtesDemo method looks like
//names is also an array.method assembly hidebysig static void '<<Main>$>g__ParamtesDemo|0_0' ( string className, string[] names ) cil managed { .custom instance void ::.ctor(uint8) = ( 01 00 01 00 00 ) .custom instance void []::.ctor() = ( 01 00 00 00 // Method begins at RVA 0x20d5 // Header size: 1 // Code size: 30 (0x1e) .maxstack 8 // { IL_0000: nop // (className + "students have:" + (",", names)); IL_0001: ldarg.0 IL_0002: ldstr "The students have:" IL_0007: ldstr "," IL_000c: ldarg.1 IL_000d: call string []::Join(string, string[]) IL_0012: call string []::Concat(string, string, string) IL_0017: call void []::WriteLine(string) // } IL_001c: nop IL_001d: ret } // end of method Program::'<<Main>$>g__ParamtesDemo|0_0'
Everything is clear, the essence is that array. We also mentioned above that if the parameters modified by params are not passed by default, there will be no errors. Why is this? Let's use the IL code to see what operations have been performed.
// Methods .method private hidebysig static void '<Main>$' ( string[] args ) cil managed { // Method begins at RVA 0x2092 // Header size: 1 // Code size: 24 (0x18) .maxstack 8 .entrypoint // ParamtesDemo("Little Class 4", <string>()); IL_0000: ldstr "Little Class 4" // The essence is to declare an empty array for us when compiling Array::Empty<string> IL_0005: call !!0[] []::Empty<string>() IL_000a: call void Program::'<<Main>$>g__ParamtesDemo|0_0'(string, string[]) // (); IL_000f: nop IL_0010: call valuetype [] []::ReadKey() IL_0015: pop // } IL_0016: nop IL_0017: ret } // end of method Program::'<Main>$'
It turns out that this has to be thanks to the compiler. If the parameter modified by params is not passed by default, it will help us generate a type ofEmpty array
, what needs to be paid attention to here is notnull
, so the code will not report an error, but there is no data.
Extended knowledge
We mentioned aboveIt is also implemented based on params. After all, the specific parameters of Format depend on the number of placeholders of the string declared earlier. While looking through the relevant code, I found a
ParamsArray
This class is used to wrap params variable parameters. In simple terms, it is convenient to operate params quickly. I found this in the Format method. The source code is as follows
public static string Format(string format, params object?[] args) { if (args == null) { throw new ArgumentNullException((format == null) ? nameof(format) : nameof(args)); } return FormatHelper(null, format, new ParamsArray(args)); }
The params parameter can also be null value, and there will be no errors by default, but it needs to be judged, otherwise the program may report errors when processing null. Here we can see that the params parameter is passed to ParamsArray for wrapping. We can take a look at the definition of the ParamsArray class itself, which is a struct type
internal readonly struct ParamsArray { //Definition is that three arrays carry data when the passed in params are different numbers respectively private static readonly object?[] s_oneArgArray = new object?[1]; private static readonly object?[] s_twoArgArray = new object?[2]; private static readonly object?[] s_threeArgArray = new object?[3]; //Define three values to store the values of the 0th, 1th and 2nd parameters of params respectively private readonly object? _arg0; private readonly object? _arg1; private readonly object? _arg2; //Borrow the most original params value private readonly object?[] _args; // When the params value is 1 public ParamsArray(object? arg0) { _arg0 = arg0; _arg1 = null; _arg2 = null; _args = s_oneArgArray; } // When the params value is 2 public ParamsArray(object? arg0, object? arg1) _arg1 = arg1; _args = s_twoArgArray; // When the params value is 3 public ParamsArray(object? arg0, object? arg1, object? arg2) _arg2 = arg2; _args = s_threeArgArray; //Package the value of the entire params directly public ParamsArray(object?[] args) //Directly get out the value and cache it int len = ; _arg0 = len > 0 ? args[0] : null; _arg1 = len > 1 ? args[1] : null; _arg2 = len > 2 ? args[2] : null; _args = args; public int Length => _args.Length; public object? this[int index] => index == 0 ? _arg0 : GetAtSlow(index); //Judge whether to retrieve the value from the hosted cache private object? GetAtSlow(int index) if (index == 1) return _arg1; if (index == 2) return _arg2; return _args[index]; }
ParamsArray is a value type, and the purpose is to wrap the value of the params parameter to provide reading-related operations. According to the 28th rule, the number of parameters or high-frequency access of most params scenes may exist on the first few elements of the array, so using ParamsArray provides a quick access method for hotspot elements, which is slightly like the design of IntegerCache in Java. This structure is of internal type and cannot be accessed outside the default assembly. When I saw it, I was curious and took a look at it. I felt that the design idea was quite thoughtful.
Summarize
This article mainly briefly talks about the essence of c# variable parameter params, and understands that it is actually a syntactic sugar, and after compilation is completed, it is still the essence ofArray
. Its advantage is that when we are not sure about the number of sets, we can flexibly use params for parameter passing without defining a collection type by ourselves. Then Microsoft implemented a ParamsArray structure internally for params to wrap the params to improve the access to params type.
At the beginning of the new year, let’s talk about my personal opinions on learning. The ideal result of learning is to abstract the knowledge you come into contact with, convert it into a concept or a way of thinking, and then refine this thinking and make it a fine-grained knowledge point. Then we continue to absorb and strengthen this thinking library through continuous contact and continuous accumulation of contacts and contacts in different fields. Then when you see a new problem, or when you need to think about it, you can quickly integrate these thinking fragments from multiple angles and get a better idea or solution to the problem. This may be a more effective state. In terms of architecture design, the previous way of thinking was a method similar to monolithic applications, with poor flexibility and poor scalability. Later, the concept of microservices became popular, and more independent services worked in coordination with each other to form a stronger aggregation force.
This is the end of this article about the detailed explanation of the C# variable parameter params example. For more related C# variable parameter params content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!