在 .NET 结构构造函数中使用 try-catch
本文关键字:try-catch 构造函数 NET 结构 | 更新日期: 2023-09-27 18:31:51
我需要一个.net结构(它模仿连接的设备内部映射),我想使用一个trycatch块,因为我正在使用Marshall.PtrToStructure()和相关GChandle的东西。但是,当我将结构字段分配放在 try catch 块中时,我收到此错误"在将控制权返回给发送者之前,必须完全分配 field1"。基本代码在没有 try catch 块的情况下工作正常。使用尝试捕获块时有什么方法可以解决此错误吗?我应该使用尝试捕获吗?
[StructLayout( LayoutKind.Sequential )]
public struct Effects
{
public UInt16 field_1;
public UInt16 field_2;
...
public Effects(byte[] effectsData)
{
GCHandle gch;
try
{
gch = GCHandle.Alloc( effectsData, GCHandleType.Pinned );
IntPtr pEffects = gch.AddrOfPinnedObject( );
this = (Effects)Marshal.PtrToStructure( pEffects, typeof(Effects ) );
}
catch (Exception ex)
{
}
finally
{
if (gch.IsAllocated)
gch.Free( );
}
}
}
构造函数必须保证如果它正常返回,则填写所有字段。
假设您的代码在try
的第一行引发异常。然后你抓住异常,吃掉它,然后返回,从来没有填过任何字段!编译器检测到这一点并禁止该程序。
吃掉每一个例外几乎可以肯定是错误的做法。如果存在未处理和意外的任意异常,是否真的要返回未初始化的结构?
如果这是你想做的,那么你可以简单地说:
public Effects(byte[] effectsData) : this() {
这将保证在 CTOR 块运行之前将字段初始化为其默认值。
但话又说回来:这真的是你想做的吗?这段代码对我来说看起来非常危险。
如果出现异常,您希望发生什么?就像你的代码现在一样,如果 try 块中发生异常,异常就会被吞噬(因为你的 catch 块是空的),并且结构是未初始化的——这正是编译器抱怨的。
如果您只需要 final-part 的 try-catch 块,则根本不要使用 catch 块,而只需尝试 final。让错误通过不捕获它们而冒泡到用户界面。没有捕获块(几乎)总是比空捕获块更好。空的 catch 块使调试成为一场噩梦,因为您的代码只是"不起作用",没有指示出了什么问题。
因此,我会按如下方式重写您的代码:
public Effects(byte[] effectsData)
{
GCHandle gch = GCHandle.Alloc( effectsData, GCHandleType.Pinned );
try
{
IntPtr pEffects = gch.AddrOfPinnedObject( );
this = (Effects)Marshal.PtrToStructure( pEffects, typeof(Effects ) );
}
finally
{
if (gch.IsAllocated)
gch.Free( );
}
}
(如果在 GCHandle.Alloc
中发生错误,gch 不会被分配,因此无需将其包含在 try-finally 块中。
将其添加到构造函数中
field_1 = 0;
field_2 = 0;
...
结构的行为与类不同。必须在构造函数中显式分配所有字段。
您必须在构造函数中设置结构的所有属性。
插入 try/catch 时,并非所有代码路径都允许设置这些属性。
您可能需要类似以下内容:
try
{
// Tries to affect something
// Then returns
return;
}
catch (Exception ex)
{
// Set default values
this.field1 = ....
}
finally
{
if (gch.IsAllocated)
gch.Free( );
}
离开构造函数之前,必须初始化结构的成员。
要么在捕获异常时抛出一些东西,要么在进入 try-catch 循环之前将数据模因初始化为合理的内容。或者,您可以将值初始化为 catch 块内的内容。
问题是空的 catch 块:如果发生异常,应该如何初始化结构?
如果您确实想忽略异常,则必须将this
初始化为 catch 块中的默认值。要将所有字段设置为零,只需使用
this = new Effects();
或者,您可以通过让异常从构造函数中传播来避免此问题。
静态成员比提供第二个构造函数的问题和混乱要少得多:
public static Effects Create(byte[] effectsData)
{
GCHandle gch;
try
{
gch = GCHandle.Alloc( effectsData, GCHandleType.Pinned );
IntPtr pEffects = gch.AddrOfPinnedObject( );
return (Effects)Marshal.PtrToStructure( pEffects, typeof(Effects) );
}
finally
{
if (gch.IsAllocated)
gch.Free( );
}
}
在这种情况下,异常处理很容易理解和维护。
你肯定知道字节数组的布局吧?使用 BitConverter
类初始化字段。这样,您无需担心固定、最终重试和其他不必要的开销。
[StructLayout( LayoutKind.Sequential )]
public struct Effects
{
public UInt16 field_1;
public UInt16 field_2;
public static Effects FromBytes(byte[] data)
{
var value = new Effects();
value.field_1 = BitConverter.ToUInt16(data, 0);
value.field_2 = BitConverter.ToUInt16(data, 2);
return value;
}
}
进行任何必要的调整以适应字节序和对齐差异。