在 .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( );
       }
    }
}

在 .NET 结构构造函数中使用 try-catch

构造函数必须保证如果它正常返回,则填写所有字段

假设您的代码在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;
    }
}

进行任何必要的调整以适应字节序和对齐差异。