in-parameters https://www.e-learn.cn/tag/parameters-0 zh-hans Using C# 7.2 in modifier for parameters with primitive types https://www.e-learn.cn/topic/2581115 <span>Using C# 7.2 in modifier for parameters with primitive types</span> <span><span lang="" about="/user/67" typeof="schema:Person" property="schema:name" datatype="">孤人</span></span> <span>2019-12-18 03:59:08</span> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><h3>问题</h3><br /><p>C# 7.2 introduced the <code>in</code> modifier for passing arguments by reference with the guarantee that the recipient will not modify the parameter.</p> <p>This article says:</p> <blockquote> <p>You should never use a non-readonly struct as the in parameters because it may negatively affect performance and could lead to an obscure behavior if the struct is mutable</p> </blockquote> <p>What does this mean for built-in primitives such as <code>int</code>, <code>double</code>?</p> <p>I would like to use <code>in</code> to express intent in code, but not at the cost of performance losses to defensive copies.</p> <p>Questions</p> <ul><li>Is it safe to pass primitive types via <code>in</code> arguments and not have defensive copies made?</li> <li>Are other commonly used framework structs such as <code>DateTime</code>, <code>TimeSpan</code>, <code>Guid</code>, ... considered <code>readonly</code> by the JIT? <ul><li>If this varies by platform, how can we find out which types are safe in a given situation?</li> </ul></li> </ul><br /><h3>回答1:</h3><br /><p>A quick test shows that, currently, yes, a defensive copy is created for built-in primitive types and structs.</p> <p>Compiling the following code with VS 2017 (.NET 4.5.2, C# 7.2, release build):</p> <pre><code>using System; class MyClass { public readonly struct Immutable { public readonly int I; public void SomeMethod() { } } public struct Mutable { public int I; public void SomeMethod() { } } public void Test(Immutable immutable, Mutable mutable, int i, DateTime dateTime) { InImmutable(immutable); InMutable(mutable); InInt32(i); InDateTime(dateTime); } void InImmutable(in Immutable x) { x.SomeMethod(); } void InMutable(in Mutable x) { x.SomeMethod(); } void InInt32(in int x) { x.ToString(); } void InDateTime(in DateTime x) { x.ToString(); } public static void Main(string[] args) { } } </code></pre> <p>yields the following result when decompiled with ILSpy:</p> <pre><code>... private void InImmutable([System.Runtime.CompilerServices.IsReadOnly] [In] ref MyClass.Immutable x) { x.SomeMethod(); } private void InMutable([System.Runtime.CompilerServices.IsReadOnly] [In] ref MyClass.Mutable x) { MyClass.Mutable mutable = x; mutable.SomeMethod(); } private void InInt32([System.Runtime.CompilerServices.IsReadOnly] [In] ref int x) { int num = x; num.ToString(); } private void InDateTime([System.Runtime.CompilerServices.IsReadOnly] [In] ref DateTime x) { DateTime dateTime = x; dateTime.ToString(); } ... </code></pre> <p>(or, if you prefer IL:)</p> <pre><code>IL_0000: ldarg.1 IL_0001: ldobj [mscorlib]System.DateTime IL_0006: stloc.0 IL_0007: ldloca.s 0 IL_0009: call instance string [mscorlib]System.DateTime::ToString() IL_000e: pop IL_000f: ret </code></pre> <br /><br /><br /><h3>回答2:</h3><br /><p>From the jit's standpoint <code>in</code> alters the calling convention for a parameter so that it is always passed by-reference. So for primitive types (which are cheap to copy) and normally passed by-value, there a small extra cost on both the caller's side and the callee's side if you use <code>in</code>. No defensive copies are made, however.</p> <p>Eg in </p> <pre><code>using System; using System.Runtime.CompilerServices; class X { [MethodImpl(MethodImplOptions.NoInlining)] static int F0(in int x) { return x + 1; } [MethodImpl(MethodImplOptions.NoInlining)] static int F1(int x) { return x + 1; } public static void Main() { int x = 33; F0(x); F0(x); F1(x); F1(x); } } </code></pre> <p>The code for <code>Main</code> is</p> <pre><code> C744242021000000 mov dword ptr [rsp+20H], 33 488D4C2420 lea rcx, bword ptr [rsp+20H] E8DBFBFFFF call X:F0(byref):int 488D4C2420 lea rcx, bword ptr [rsp+20H] E8D1FBFFFF call X:F0(byref):int 8B4C2420 mov ecx, dword ptr [rsp+20H] E8D0FBFFFF call X:F1(int):int 8B4C2420 mov ecx, dword ptr [rsp+20H] E8C7FBFFFF call X:F1(int):int </code></pre> <p>Note because of the <code>in</code> x can't be enregistered.</p> <p>And the code for <code>F0 &amp; F1</code> shows the former must now read the value from the byref:</p> <pre><code>;; F0 8B01 mov eax, dword ptr [rcx] FFC0 inc eax C3 ret ;; F1 8D4101 lea eax, [rcx+1] C3 ret </code></pre> <p>This extra cost can usually be undone if the jit inlines, though not always.</p> <br /><br /><br /><h3>回答3:</h3><br /><p>With the current compiler, defensive copies do indeed appear to be made for both 'primitive' value types and other non-readonly structs. Specifically, they are generated similarly to how they are for <code>readonly</code> fields: when accessing a property or method that could potentially mutate the contents. The copies appear <strong>at each call site</strong> to a potentially mutating member, so if you invoke <em>n</em> such members, you'll end up making <em>n</em> defensive copies. As with <code>readonly</code> fields, you can avoid multiple copies by manually copying the original to a local.</p> <p>Take a look at this suite of examples. You can view both the IL and the JIT assembly.</p> <blockquote> <p>Is it safe to pass primitive types via in arguments and not have defensive copies made?</p> </blockquote> <p>It depends on whether you access a method or property on the <code>in</code> parameter. If you do, you may see defensive copies. If not, you probably won't:</p> <pre><code>// Original: int In(in int _) { _.ToString(); _.GetHashCode(); return _ &gt;= 0 ? _ + 42 : _ - 42; } // Decompiled: int In([In] [IsReadOnly] ref int _) { int num = _; num.ToString(); // invoke on copy num = _; num.GetHashCode(); // invoke on second copy if (_ &lt; 0) return _ - 42; // use original in arithmetic return _ + 42; } </code></pre> <blockquote> <p>Are other commonly used framework structs such as DateTime, TimeSpan, Guid, ... considered readonly by [the compiler]?</p> </blockquote> <p>No, defensive copies will still be made at call sites for potentially mutating members on <code>in</code> parameters of these types. What's interesting, though, is that not <em>all</em> methods and properties are considered 'potentially mutating'. I noticed that if I called a default method implementation (e.g., <code>ToString</code> or <code>GetHashCode</code>), no defensive copies were emitted. However, as soon as I overrode those methods, the compiler created copies:</p> <pre><code>struct WithDefault {} struct WithOverride { public override string ToString() =&gt; "RO"; } // Original: void In(in WithDefault d, in WithOverride o) { d.ToString(); o.ToString(); } // Decompiled: private void In([In] [IsReadOnly] ref WithDefault d, [In] [IsReadOnly] ref WithOverride o) { d.ToString(); // invoke on original WithOverride withOverride = o; withOverride.ToString(); // invoke on copy } </code></pre> <blockquote> <p>If this varies by platform, how can we find out which types are safe in a given situation?</p> </blockquote> <p>Well, all types are 'safe'--the copies ensure that. I assume you're asking which types will avoid a defensive copy. As we've seen above, it's more complicated than "what's the type of the parameter"? There's no single copy: the copies are emitted at certain references to <code>in</code> parameters, e.g., where the reference is an invocation target. If no such references are present, no copies need to be made. Moreover, the decision whether to copy can depend on whether you invoke a member that is known to be safe or 'pure' vs. a member which could potentially mutate the a value type's contents.</p> <p>For now, certain default methods seem to be treated as pure, and the compiler avoids making copies in those cases. If I had to guess, this is a result of preexisting behavior, and the compiler is utilizing some notion of 'read only' references that was originally developed for <code>readonly</code> fields. As you can see below (or in SharpLab), the behavior is similar. Note how the IL uses <code>ldflda</code> (load field <em>by address</em>) to push the invocation target onto the stack when calling <code>WithDefault.ToString</code>, but uses a <code>ldfld</code>, <code>stloc</code>, <code>ldloca</code> sequence to push a <em>copy</em> onto the stack when invoking <code>WithOverride.ToString</code>:</p> <pre><code>struct WithDefault {} struct WithOverride { public override string ToString() =&gt; "RO"; } static readonly WithDefault D; static readonly WithOverride O; // Original: static void Test() { D.ToString(); O.ToString(); } // IL Disassembly: .method private hidebysig static void Test () cil managed { .maxstack 1 .locals init ([0] valuetype Overrides/WithOverride) // [WithDefault] Invoke on original by address: IL_0000: ldsflda valuetype Overrides/WithDefault Overrides::D IL_0005: constrained. Overrides/WithDefault IL_000b: callvirt instance string [mscorlib]System.Object::ToString() IL_0010: pop // [WithOverride] Copy original to local, invoke on copy by address: IL_0011: ldsfld valuetype Overrides/WithOverride Overrides::O IL_0016: stloc.0 IL_0017: ldloca.s 0 IL_0019: constrained. Overrides/WithOverride IL_001f: callvirt instance string [mscorlib]System.Object::ToString() IL_0024: pop IL_0025: ret } </code></pre> <p>That said, now that read only references will presumably become more common, the 'white list' of methods that can be invoked <em>without</em> defensive copies may grow in the future. For now, it seems somewhat arbitrary.</p> <br /><br /><br /><h3>回答4:</h3><br /><blockquote> <p>What does this mean for built-in primitives such as int, double?</p> </blockquote> <p>Nothing, <code>int</code> and <code>double</code> and all other built-in "primitives" are immutable. You can't mutate a <code>double</code>, an <code>int</code> or a <code>DateTime</code>. A typical framework type that would not be a good candidate is <code>System.Drawing.Point</code> for instance.</p> <p>To be honest, the documentation could be a little bit clearer; readonly is a confusing term in this context, it should simply say the type should be immutable.</p> <p>There is no rule to know if any given type is immutable or not; only a close inspection of the API can give you an idea or, if you are lucky, the documentation might state if it is or not.</p> <br /><br /><p>来源:<code>https://stackoverflow.com/questions/50777828/using-c-sharp-7-2-in-modifier-for-parameters-with-primitive-types</code></p></div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field--label">标签</div> <div class="field--items"> <div class="field--item"><a href="/tag/c" hreflang="zh-hans">c#</a></div> <div class="field--item"><a href="/tag/net" hreflang="zh-hans">.net</a></div> <div class="field--item"><a href="/tag/optimization" hreflang="zh-hans">optimization</a></div> <div class="field--item"><a href="/tag/c-72" hreflang="zh-hans">c#-7.2</a></div> <div class="field--item"><a href="/tag/parameters-0" hreflang="zh-hans">in-parameters</a></div> </div> </div> Tue, 17 Dec 2019 19:59:08 +0000 孤人 2581115 at https://www.e-learn.cn Using C# 7.2 in modifier for parameters with primitive types https://www.e-learn.cn/topic/790428 <span>Using C# 7.2 in modifier for parameters with primitive types</span> <span><span lang="" about="/user/83" typeof="schema:Person" property="schema:name" datatype="">蓝咒</span></span> <span>2019-11-30 12:38:15</span> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><div class="alert alert-danger" role="alert"> <p>C# 7.2 introduced the <code>in</code> modifier for passing arguments by reference with the guarantee that the recipient will not modify the parameter.</p> <p>This <a href="https://blogs.msdn.microsoft.com/seteplia/2018/03/07/the-in-modifier-and-the-readonly-structs-in-c/" rel="nofollow">article</a> says:</p> <blockquote> <p>You should never use a non-readonly struct as the in parameters because it may negatively affect performance and could lead to an obscure behavior if the struct is mutable</p> </blockquote> <p>What does this mean for built-in primitives such as <code>int</code>, <code>double</code>?</p> <p>I would like to use <code>in</code> to express intent in code, but not at the cost of performance losses to defensive copies.</p> <p>Questions</p> <ul><li>Is it safe to pass primitive types via <code>in</code> arguments and not have defensive copies made?</li> <li>Are other commonly used framework structs such as <code>DateTime</code>, <code>TimeSpan</code>, <code>Guid</code>, ... considered <code>readonly</code> by the JIT? <ul><li>If this varies by platform, how can we find out which types are safe in a given situation?</li> </ul></li> </ul></div><div class="panel panel-info"><div class="panel-heading"></div><div class="panel-body"> <p>A quick test shows that, currently, yes, a defensive copy is created for built-in primitive types and structs.</p> <p>Compiling the following code with VS 2017 (.NET 4.5.2, C# 7.2, release build):</p> <pre><code>using System; class MyClass { public readonly struct Immutable { public readonly int I; public void SomeMethod() { } } public struct Mutable { public int I; public void SomeMethod() { } } public void Test(Immutable immutable, Mutable mutable, int i, DateTime dateTime) { InImmutable(immutable); InMutable(mutable); InInt32(i); InDateTime(dateTime); } void InImmutable(in Immutable x) { x.SomeMethod(); } void InMutable(in Mutable x) { x.SomeMethod(); } void InInt32(in int x) { x.ToString(); } void InDateTime(in DateTime x) { x.ToString(); } public static void Main(string[] args) { } } </code></pre> <p>yields the following result when decompiled with ILSpy:</p> <pre><code>... private void InImmutable([System.Runtime.CompilerServices.IsReadOnly] [In] ref MyClass.Immutable x) { x.SomeMethod(); } private void InMutable([System.Runtime.CompilerServices.IsReadOnly] [In] ref MyClass.Mutable x) { MyClass.Mutable mutable = x; mutable.SomeMethod(); } private void InInt32([System.Runtime.CompilerServices.IsReadOnly] [In] ref int x) { int num = x; num.ToString(); } private void InDateTime([System.Runtime.CompilerServices.IsReadOnly] [In] ref DateTime x) { DateTime dateTime = x; dateTime.ToString(); } ... </code></pre> <p>(or, if you prefer IL:)</p> <pre><code>IL_0000: ldarg.1 IL_0001: ldobj [mscorlib]System.DateTime IL_0006: stloc.0 IL_0007: ldloca.s 0 IL_0009: call instance string [mscorlib]System.DateTime::ToString() IL_000e: pop IL_000f: ret </code></pre> </div></div><div class="panel panel-info"><div class="panel-heading"></div><div class="panel-body"> <p>From the jit's standpoint <code>in</code> alters the calling convention for a parameter so that it is always passed by-reference. So for primitive types (which are cheap to copy) and normally passed by-value, there a small extra cost on both the caller's side and the callee's side if you use <code>in</code>. No defensive copies are made, however.</p> <p>Eg in </p> <pre><code>using System; using System.Runtime.CompilerServices; class X { [MethodImpl(MethodImplOptions.NoInlining)] static int F0(in int x) { return x + 1; } [MethodImpl(MethodImplOptions.NoInlining)] static int F1(int x) { return x + 1; } public static void Main() { int x = 33; F0(x); F0(x); F1(x); F1(x); } } </code></pre> <p>The code for <code>Main</code> is</p> <pre><code> C744242021000000 mov dword ptr [rsp+20H], 33 488D4C2420 lea rcx, bword ptr [rsp+20H] E8DBFBFFFF call X:F0(byref):int 488D4C2420 lea rcx, bword ptr [rsp+20H] E8D1FBFFFF call X:F0(byref):int 8B4C2420 mov ecx, dword ptr [rsp+20H] E8D0FBFFFF call X:F1(int):int 8B4C2420 mov ecx, dword ptr [rsp+20H] E8C7FBFFFF call X:F1(int):int </code></pre> <p>Note because of the <code>in</code> x can't be enregistered.</p> <p>And the code for <code>F0 &amp; F1</code> shows the former must now read the value from the byref:</p> <pre><code>;; F0 8B01 mov eax, dword ptr [rcx] FFC0 inc eax C3 ret ;; F1 8D4101 lea eax, [rcx+1] C3 ret </code></pre> <p>This extra cost can usually be undone if the jit inlines, though not always.</p> </div></div><div class="panel panel-info"><div class="panel-heading"></div><div class="panel-body"> <p>With the current compiler, defensive copies do indeed appear to be made for both 'primitive' value types and other non-readonly structs. Specifically, they are generated similarly to how they are for <code>readonly</code> fields: when accessing a property or method that could potentially mutate the contents. The copies appear <strong>at each call site</strong> to a potentially mutating member, so if you invoke <em>n</em> such members, you'll end up making <em>n</em> defensive copies. As with <code>readonly</code> fields, you can avoid multiple copies by manually copying the original to a local.</p> <p>Take a look at <a href="https://sharplab.io/#v2:D4AQDABAygFghgJwA4Bk4CMB0AlArgOwBcBLAWwFMBuAWACg6QAmCABQTOJIDdyBnCAN50IIiMSIQAkvgAU4sRID6ASkERFmACoB7KIXb4A5jOWV1mAOLlCACTi8YAYW0ATcibMgA7OogA+AF4ISAB+XwBqCAAWZgAuXwBaaMYzAF9hUXFCCGxyADMZBHyFbJU1DR09A2NTcytbeydXd1qK3X1xGs8fRQhImLSMkSyIADU4ABs5JVUBc0qOow866zsHZzdltqrO5e8I5MHadPpaJggAWVxCDAnyKtwAY0J+IVpRCF59J+zsAHU1CAAMwQbQ8BDsNwQEAARkgC2qJggAT8EAARP80UchtColJZPJ/upZvN2ojWpZVo0Ni0zNtFl0ICcPiA8bkCkU8jkAWU5vTyXTKQ11s0tloybtaszRKyxpMZETeaSdksKfU1k1NhSEZKjicGMxcnAXAB5fATACeD2erxxRWN2nNFs+32eORNgJBYPIEOIUNh8IlquRqIxJqxTLoONl0mm7uJ5XFKsZGnV1NF2qDjOlIll7MKxWwHqV/N1K2FmtpyoZyxzuLlUyLCb5SZraqpIq1gp1qr1UbOzCs+B9xEetveoi+CB+3M9oPBkPI0LhEB7NRD6MxRw+9pcjstLunbqbc2B859i+XgeTSJRm/D2Inubxmj4hCRbw+H1jMVMOO/sjDgA7tyJh/k+X6xsB7pgTQEH6hBADaABSnBDiOjwyIQFpIOQ2gFFkyjKAANBAqGEOh7CYdhuH4QqfxEaR5GUaOWE4XhBRFkRAC60Z4tIAA8mh+HGmjNtWArlhqNJimutZ8ficaKiSpa9lJ6ZdhJZZ1jGBL4PGJatpJqYdpWslZvJxx0EAA" rel="nofollow">this suite of examples</a>. You can view both the IL and the JIT assembly.</p> <blockquote> <p>Is it safe to pass primitive types via in arguments and not have defensive copies made?</p> </blockquote> <p>It depends on whether you access a method or property on the <code>in</code> parameter. If you do, you may see defensive copies. If not, you probably won't:</p> <pre><code>// Original: int In(in int _) { _.ToString(); _.GetHashCode(); return _ &gt;= 0 ? _ + 42 : _ - 42; } // Decompiled: int In([In] [IsReadOnly] ref int _) { int num = _; num.ToString(); // invoke on copy num = _; num.GetHashCode(); // invoke on second copy if (_ &lt; 0) return _ - 42; // use original in arithmetic return _ + 42; } </code></pre> <blockquote> <p>Are other commonly used framework structs such as DateTime, TimeSpan, Guid, ... considered readonly by [the compiler]?</p> </blockquote> <p>No, defensive copies will still be made at call sites for potentially mutating members on <code>in</code> parameters of these types. What's interesting, though, is that not <em>all</em> methods and properties are considered 'potentially mutating'. I noticed that if I called a default method implementation (e.g., <code>ToString</code> or <code>GetHashCode</code>), no defensive copies were emitted. However, as soon as I overrode those methods, the compiler created copies:</p> <pre><code>struct WithDefault {} struct WithOverride { public override string ToString() =&gt; "RO"; } // Original: void In(in WithDefault d, in WithOverride o) { d.ToString(); o.ToString(); } // Decompiled: private void In([In] [IsReadOnly] ref WithDefault d, [In] [IsReadOnly] ref WithOverride o) { d.ToString(); // invoke on original WithOverride withOverride = o; withOverride.ToString(); // invoke on copy } </code></pre> <blockquote> <p>If this varies by platform, how can we find out which types are safe in a given situation?</p> </blockquote> <p>Well, all types are 'safe'--the copies ensure that. I assume you're asking which types will avoid a defensive copy. As we've seen above, it's more complicated than "what's the type of the parameter"? There's no single copy: the copies are emitted at certain references to <code>in</code> parameters, e.g., where the reference is an invocation target. If no such references are present, no copies need to be made. Moreover, the decision whether to copy can depend on whether you invoke a member that is known to be safe or 'pure' vs. a member which could potentially mutate the a value type's contents.</p> <p>For now, certain default methods seem to be treated as pure, and the compiler avoids making copies in those cases. If I had to guess, this is a result of preexisting behavior, and the compiler is utilizing some notion of 'read only' references that was originally developed for <code>readonly</code> fields. As you can see below (or <a href="https://sharplab.io/#v2:C4LglgNgPgAgTAAgEoFMCGATA8gOwgTwDEwUIMAVFAZ2CoQG8BYAKATYRoCcBXAY2AQB1MMAAWAERQAzNNwgD6AXxbsOwHvyEjRWAG4pOnMBhQMEMAMwIA9vsPHTMAIwAGBOWsBldWBwBzAAoASgQAXgA+BAAiJCwogG4EZVZ2FXZnADYETnQMazx8LTFJGTkBcXi0tkzs3PyCIp07IxMELErmKvMnLJgAFndqYGCGLtVxADoPbyN/YI7VVSwprx85oIX2ZMUgA=" rel="nofollow">in SharpLab</a>), the behavior is similar. Note how the IL uses <code>ldflda</code> (load field <em>by address</em>) to push the invocation target onto the stack when calling <code>WithDefault.ToString</code>, but uses a <code>ldfld</code>, <code>stloc</code>, <code>ldloca</code> sequence to push a <em>copy</em> onto the stack when invoking <code>WithOverride.ToString</code>:</p> <pre><code>struct WithDefault {} struct WithOverride { public override string ToString() =&gt; "RO"; } static readonly WithDefault D; static readonly WithOverride O; // Original: static void Test() { D.ToString(); O.ToString(); } // IL Disassembly: .method private hidebysig static void Test () cil managed { .maxstack 1 .locals init ([0] valuetype Overrides/WithOverride) // [WithDefault] Invoke on original by address: IL_0000: ldsflda valuetype Overrides/WithDefault Overrides::D IL_0005: constrained. Overrides/WithDefault IL_000b: callvirt instance string [mscorlib]System.Object::ToString() IL_0010: pop // [WithOverride] Copy original to local, invoke on copy by address: IL_0011: ldsfld valuetype Overrides/WithOverride Overrides::O IL_0016: stloc.0 IL_0017: ldloca.s 0 IL_0019: constrained. Overrides/WithOverride IL_001f: callvirt instance string [mscorlib]System.Object::ToString() IL_0024: pop IL_0025: ret } </code></pre> <p>That said, now that read only references will presumably become more common, the 'white list' of methods that can be invoked <em>without</em> defensive copies may grow in the future. For now, it seems somewhat arbitrary.</p> </div></div><div class="panel panel-info"><div class="panel-heading"></div><div class="panel-body"> <blockquote> <p>What does this mean for built-in primitives such as int, double?</p> </blockquote> <p>Nothing, <code>int</code> and <code>double</code> and all other built-in "primitives" are immutable. You can't mutate a <code>double</code>, an <code>int</code> or a <code>DateTime</code>. A typical framework type that would not be a good candidate is <code>System.Drawing.Point</code> for instance.</p> <p>To be honest, the documentation could be a little bit clearer; readonly is a confusing term in this context, it should simply say the type should be immutable.</p> <p>There is no rule to know if any given type is immutable or not; only a close inspection of the API can give you an idea or, if you are lucky, the documentation might state if it is or not.</p> </div></div><div class="alert alert-warning" role="alert"><p>来源:<code>https://stackoverflow.com/questions/50777828/using-c-sharp-7-2-in-modifier-for-parameters-with-primitive-types</code></p></div></div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field--label">标签</div> <div class="field--items"> <div class="field--item"><a href="/tag/c" hreflang="zh-hans">c#</a></div> <div class="field--item"><a href="/tag/net" hreflang="zh-hans">.net</a></div> <div class="field--item"><a href="/tag/optimization" hreflang="zh-hans">optimization</a></div> <div class="field--item"><a href="/tag/c-72" hreflang="zh-hans">c#-7.2</a></div> <div class="field--item"><a href="/tag/parameters-0" hreflang="zh-hans">in-parameters</a></div> </div> </div> Sat, 30 Nov 2019 04:38:15 +0000 蓝咒 790428 at https://www.e-learn.cn