C# 8.0 本地和静态本地函数为什么使用它们更好或不更好?
C# 8.0 local and static local function why using them is better or not?
在一个开源项目中发现了以下代码,我很惊讶原始编码人员如何在这里使用本地和静态本地函数:
public static Win32Error CallMethodWithTypedBuf<TOut, TSize>(SizeFunc<TSize> getSize, PtrFunc<TSize> method, out TOut result, Func<IntPtr, TSize, TOut> outConverter = null, Win32Error? bufErr = null) where TSize : struct, IConvertible
{
TSize sz = default;
result = default;
var err = (getSize ?? GetSize)(ref sz);
if (err.Failed && (bufErr == null || bufErr.Value != err) && !buffErrs.Contains(err)) return err;
using (var buf = new SafeHGlobalHandle(sz.ToInt32(null)))
{
err = method(buf.DangerousGetHandle(), ref sz);
if (err.Succeeded)
result = (outConverter ?? Conv)(buf.DangerousGetHandle(), sz);
return err;
}
Win32Error GetSize(ref TSize sz1) => method(IntPtr.Zero, ref sz1);
static TOut Conv(IntPtr p, TSize s) => p == IntPtr.Zero ? default : p.Convert<TOut>(Convert.ToUInt32(s));
}
所以我重写了一些实际上在一个地方有逻辑的东西,甚至可以在旧的编译器中工作(就像 10 年前写的那样)。要意识到以下代码实际上更小,消耗更少的调用堆栈,并且可以通过 if-branch 预测更好地优化:
public static Win32Error CallMethodWithTypedBuf<TOut, TSize>(SizeFunc<TSize> getSize, PtrFunc<TSize> method, out TOut result, Func<IntPtr, TSize, TOut> outConverter = null, Win32Error? bufErr = null) where TSize : struct, IConvertible
{
TSize sz = default;
result = default;
var err = (null != getSize ? getSize(ref sz) : method(IntPtr.Zero, ref sz));
if (err.Failed && (bufErr == null || bufErr.Value != err) && !buffErrs.Contains(err)) return err;
using (var buf = new SafeHGlobalHandle(sz.ToInt32(null)))
{
err = method(buf.DangerousGetHandle(), ref sz);
if (err.Succeeded)
{
IntPtr p = buf.DangerousGetHandle();
result = (null != outConverter) ? outConverter(p, sz) : (p == IntPtr.Zero ? default : p.Convert<TOut>(Convert.ToUInt32(sz)));
}
return err;
}
}
所以我很困惑为什么有人会像原始编码员那样使用本地和静态函数。在这一点上,我认为人们很生气,不知道该如何处理自己,并开始在全球范围内制造混乱。请告诉我我错了,我们都应该在我们的代码中立即使用局部和静态局部函数吗?
我认为从 C# 的开发方式可以清楚地看出,readability/ease 使用是该语言的核心支柱之一。大多数新功能并没有真正提供任何真正的新功能,除了让您可以选择以不同的方式编写内容,希望更多 readable/easier.
几乎总是可以选择使用新功能编写的内容并在不使用它的情况下重写它。它不会使新功能无效。
从Local Functions相关的设计笔记中,可以看出其意图:
https://github.com/dotnet/roslyn/issues/3911
We agree that the scenario is useful. You want a helper function. You
are only using it from within a single function, and it likely uses
variables and type parameters that are in scope in that containing
function. On the other hand, unlike a lambda you don't need it as a
first class object, so you don't care to give it a delegate type and
allocate an actual delegate object. Also you may want it to be
recursive or generic, or to implement it as an iterator.
在您给出的示例中,我发现原始代码更具可读性。
对旧编译器的支持并不重要,如果我的项目针对新编译器,我为什么要关心旧编译器?如果我这样做,它的尽头在哪里? C#7? C#6? 4?
性能比 readability/maintainability 代码次要,只有当某些东西被证明是你的瓶颈时,你才开始优化它。
所以有选择是好的。
当您将局部函数与 lambda 函数进行比较时,局部函数的更多优势就会显现出来,这里有一个很好的答案:
在一个开源项目中发现了以下代码,我很惊讶原始编码人员如何在这里使用本地和静态本地函数:
public static Win32Error CallMethodWithTypedBuf<TOut, TSize>(SizeFunc<TSize> getSize, PtrFunc<TSize> method, out TOut result, Func<IntPtr, TSize, TOut> outConverter = null, Win32Error? bufErr = null) where TSize : struct, IConvertible
{
TSize sz = default;
result = default;
var err = (getSize ?? GetSize)(ref sz);
if (err.Failed && (bufErr == null || bufErr.Value != err) && !buffErrs.Contains(err)) return err;
using (var buf = new SafeHGlobalHandle(sz.ToInt32(null)))
{
err = method(buf.DangerousGetHandle(), ref sz);
if (err.Succeeded)
result = (outConverter ?? Conv)(buf.DangerousGetHandle(), sz);
return err;
}
Win32Error GetSize(ref TSize sz1) => method(IntPtr.Zero, ref sz1);
static TOut Conv(IntPtr p, TSize s) => p == IntPtr.Zero ? default : p.Convert<TOut>(Convert.ToUInt32(s));
}
所以我重写了一些实际上在一个地方有逻辑的东西,甚至可以在旧的编译器中工作(就像 10 年前写的那样)。要意识到以下代码实际上更小,消耗更少的调用堆栈,并且可以通过 if-branch 预测更好地优化:
public static Win32Error CallMethodWithTypedBuf<TOut, TSize>(SizeFunc<TSize> getSize, PtrFunc<TSize> method, out TOut result, Func<IntPtr, TSize, TOut> outConverter = null, Win32Error? bufErr = null) where TSize : struct, IConvertible
{
TSize sz = default;
result = default;
var err = (null != getSize ? getSize(ref sz) : method(IntPtr.Zero, ref sz));
if (err.Failed && (bufErr == null || bufErr.Value != err) && !buffErrs.Contains(err)) return err;
using (var buf = new SafeHGlobalHandle(sz.ToInt32(null)))
{
err = method(buf.DangerousGetHandle(), ref sz);
if (err.Succeeded)
{
IntPtr p = buf.DangerousGetHandle();
result = (null != outConverter) ? outConverter(p, sz) : (p == IntPtr.Zero ? default : p.Convert<TOut>(Convert.ToUInt32(sz)));
}
return err;
}
}
所以我很困惑为什么有人会像原始编码员那样使用本地和静态函数。在这一点上,我认为人们很生气,不知道该如何处理自己,并开始在全球范围内制造混乱。请告诉我我错了,我们都应该在我们的代码中立即使用局部和静态局部函数吗?
我认为从 C# 的开发方式可以清楚地看出,readability/ease 使用是该语言的核心支柱之一。大多数新功能并没有真正提供任何真正的新功能,除了让您可以选择以不同的方式编写内容,希望更多 readable/easier.
几乎总是可以选择使用新功能编写的内容并在不使用它的情况下重写它。它不会使新功能无效。
从Local Functions相关的设计笔记中,可以看出其意图: https://github.com/dotnet/roslyn/issues/3911
We agree that the scenario is useful. You want a helper function. You are only using it from within a single function, and it likely uses variables and type parameters that are in scope in that containing function. On the other hand, unlike a lambda you don't need it as a first class object, so you don't care to give it a delegate type and allocate an actual delegate object. Also you may want it to be recursive or generic, or to implement it as an iterator.
在您给出的示例中,我发现原始代码更具可读性。 对旧编译器的支持并不重要,如果我的项目针对新编译器,我为什么要关心旧编译器?如果我这样做,它的尽头在哪里? C#7? C#6? 4?
性能比 readability/maintainability 代码次要,只有当某些东西被证明是你的瓶颈时,你才开始优化它。
所以有选择是好的。
当您将局部函数与 lambda 函数进行比较时,局部函数的更多优势就会显现出来,这里有一个很好的答案: