考虑以下C#代码:
IntPtr native = GetNativeError(/* parameters here */);
return new ManagedError(native);
然后客户端代码以下列方式检查错误:
ManagedError err = /* get it with the code above */;
if(err.IsOk()) {
/* Success */
}
此处的内存分配可以以if
语句为代价保存:
IntPtr native = GetNativeError(/* parameters here */);
if(native == IntPtr.Zero) {
return null;
} else {
return new ManagedError(native);
}
然后客户端代码将检查错误,如下所示:
ManagedError err = /* get it with the code above */;
if(err == null) {
/* Success */
}
我的问题是:哪种方法更快?第一个有额外的内存分配。第二个有额外的if
声明。
更新:我的意思是,在成功案例中。错误情况很少发生,可以慢。
答案 0 :(得分:7)
首先,您已经双向编写了代码。如果您想知道哪种方式具有更好的性能运行两个程序并测量哪个程序具有更好的性能。这是唯一方式来准确回答性能问题,所以请拿出秒表。
如果您无法衡量哪一个具有更好的性能,那么显然哪个具有更好的性能无关紧要,因为无法检测到差异。不可观察的差异是无关紧要的。
现在让我们考虑你的具体问题。假设您决定在常见的成功案例中保存内存分配。 正确的解决方案不是为null赋予特殊含义。正确的解决方案是使用null对象模式。那就是创建一个对象的特殊实例,这个实例总是在你使用null的地方使用。
class ManagedError
{
public static readonly Success = new ManagedError(IntPtr.Zero);
private ManagedError(IntPtr hr) { ... }
public ManagedError FromNative(IntPtr hr)
{
if (hr == IntPtr.Zero) return Success;
return new ManagedError(hr);
}
}
...
IntPtr native = GetNativeError(/* parameters here */);
return ManagedError.FromNative(native);
完成。你总是得到一个有效的对象,而且你不会在常见情况下做任何分配。
另外,正如另一个答案所述:为什么这不是一个结构? 为什么要进行任何引用类型的堆内存分配?如果事物只是一个intptr的包装器那么它应该是一个结构;它会像intptr一样便宜!
如果你把它变成一个结构,那么这就变得更容易了,因为你只需使用结构的默认实例作为空对象!
struct ManagedError
{
public static readonly Success = default(ManagedError);
private readonly IntPtr hr;
public ManagedError(IntPtr hr) { this.hr = hr }
}
你已经完成了;根本没有堆分配。你只需要完成intptr。
答案 1 :(得分:1)
一般来说第二种方式对我来说更清楚:ManagedError
不是错误但是成功是ManagedResult
(例如参见使用{{1}的COM API }},可以是HRESULT
(或一般S_OK
)或S_*
(例如E_*
)。
我会说,一般来说,分配一个无用的对象并不是一个好主意......但它甚至不是一个坏主意(开销非常小,因为如果对象非常短暂,GC会破坏它立刻)。仍然是针对.NET模式(使用E_FAIL
返回错误,或Exception
)...所以即使在这里我也没有看到大的加号或大的减号。
请注意,如果bool
然后将其设为sizeof(ManagedError) <= IntPtr.Size
,则可以“免费”分配和传递(因为带有struct
的{{1}}没有针对struct
或一般针对托管引用的任何开销