繁体   English   中英

处理NULL的最佳方法

[英]Best way to handle a NULL

在我的函数的顶部,我正在尝试最好的方法来处理在C#中进入我的过程的null。 检查和处理null的最佳方法是什么?为什么? 我已经添加了我正在使用的完整代码,Resharper告诉我使用选项#1。 通常我按照它说的做,因为我理解为什么它会使它更有效率。 这次虽然我不确定所以我必须问。

Option #1
if (sender == null) return;

// Code goes here

or

Option #2
if (sender != null)
{ 
     // Code goes here
}

Complete Code
        private void EmployeeMouseHoverToolTip(object sender, EventArgs e)
        {
            if (sender != null)
            {
                var sUserIdentifier = ((C1TextBox)sender).Text;
                var userIdentifier = Guid.Empty;
                if (Utilities.IsGuid(sUserIdentifier))
                {
                    userIdentifier = new Guid(sUserIdentifier);
                }

                var toolTipText = Utilities.UserIdentifierToName(userIdentifier);
                c1SuperTooltip.SetToolTip(sender as C1TextBox, toolTipText);
            }
        }

最好的代码是禁止null (而不是你正在做的)。 这并不总是可行的(有时以有意义的方式处理null很重要) - 但在大多数情况下它是。

然后你需要做的就是(在防御性编码中)添加一个null检查并抛出一个异常:

if (arg == null)
    throw new ArgumentNullException("arg");

.NET框架和良好库中的许多(如果不是大多数)方法都是这样做的。

除此之外,事件的sender 永远不应该为null ,我要说检查它是多余的。 如果将null传递给此事件,则代码会出现严重错误。

处理null的方式(通过静默吞噬它并且什么也不做)可能会掩盖应用程序中的严重错误,并且很少(如果有的话)适当。 代码中的错误应引起可疑行为,而不是席卷地毯。

为什么不假装从不发生空引用,并且不捕获NullPointerException?

您获得了堆栈跟踪,大量信息,并将其作为例外处理。

在我看来,resharper正在建议选项1,因为它使得更容易阅读代码。 你最终得到:

  • 减少缩进
  • 断言其要求并在方法顶部对它们作出反应的代码(如果发送方为空,我立即返回)
  • 代码通常更容易维护,因为它更清晰

就性能而言,可能差别不大(尽管对您来说很重要,但要测量它 )。 无论如何,没有什么可以阻止JIT编译器将一个表单重写到另一个表单,如果他们无法通过C#编译器输出相同的MSIL。

这是一个事件处理程序,只应由控件调用以响应事件(从不直接由您自己的代码),因此您不应该关心空检查甚至是对sender参数的类型检查(如果您只附加此事件处理程序到同一类型的控件)。 我会这样做:

private void EmployeeMouseHoverToolTip(object sender, EventArgs e) {  
  var txtBox = (C1TextBox)sender;
  var sUserIdentifier = txtBox.Text;
  var userIdentifier = Utilities.IsGuid(sUserIdentifier) ? 
    new Guid(sUserIdentifier) : 
    Guid.Empty;
  var toolTipText = Utilities.UserIdentifierToName(userIdentifier);
  c1SuperTooltip.SetToolTip(txtBox, toolTipText);
}

实际上,我更进一步,将逻辑分离,从逻​​辑中获取工具提示文本,以便读取和更新UI。 像这样的东西:

private void EmployeeMouseHoverToolTip(object sender, EventArgs e) {  
  var txtBox = (C1TextBox)sender;
  var toolTipText = ResolveUpdatedTooltipText(txtBox.Text);
  c1SuperTooltip.SetToolTip(txtBox, toolTipText);
}

private string ResolveUpdatedTooltipText(string sUserIdentifier) {
  var userIdentifier = ResolveGuid(sUserIdentifier);
  return Utilities.UserIdentifierToName(userIdentifier);
}

private Guid ResolveGuid(string sUserIdentifier) {
  return Utilities.IsGuid(sUserIdentifier) ? 
    new Guid(sUserIdentifier) : 
    Guid.Empty;
}

因此,您不应使用您提供的任何选项。

不要检查它。

如果你得到空值,你已经将处理程序添加到了你不应该拥有的东西中。 如果其他一些bug导致它,你应该使用WinForms的全局异常处理程序处理它,这样程序就不会轰炸 ,记录它,并将日志上传到你的站点,无论你怎样检查这些错误。

我个人更喜欢第一种选择

if (sender == null) return;

它减少了嵌套并提高了可读性。

我通常选择#1选项。 我觉得它更干净,意思更清晰。 无论谁在阅读代码,都知道如果我们已经安全地通过了空检查并且已经退出,那么我们就没有机会在稍后的发送方中使用空值。

我更喜欢

if (sender == null) return;

使用它,代码中的嵌套操作较少,如果存在null则提前退出。

Resharper喜欢选项1,因为它是一个先决条件检查器。 如果不满足前提条件,则提前退货。

通常,早期返回对代码可读性具有破坏性,但在这种情况下,它是非常易读的。

这样,您可以轻松添加额外的前置条件检查,例如检查EventArgs e的内容,而无需重做主函数代码。

你可以在为对象赋值时使用DBNull类...

UserName = DBNull.Value != reader["UserName"] ? reader["UserName"].ToString() : default(string);

在这里您的确切示例中,请查看您正在执行的操作的反转,因此如果sender为null,请提前退出。 它读得更好(IMO)并导致更少的嵌套。

所以在这个例子中选项#1

如果你不打算处理null发送者,那么我会选择第一个选项(并确保它是处理程序中的第一行,因此它不会被其他代码隐藏)。

如果你认为你最终可以处理一个null发送者,我会选择第二个选项,因为它为你提供了一个更好的方法来处理事情以及为处理程序维护一个返回点。

性能影响很小,所以我甚至不担心。 选项1更好,因为它更具可读性和更少混浊......

更具可读性,因为条件不被否定,并且没有不必要的范围块。

选项#1我想会降低Cyclomatic的复杂性。 在选项#2中,如果存在另一个if条件,那么它将归入if子句并增加复杂性

选项#1的变体,可以立即返回,也可以抛出异常。 诀窍是知道使用哪种方法。 我的经验法则是,如果它是公共接口的一部分,那么抛出异常。 如果它是您在框架中深入控制的东西,那么只需立即返回并处理该级别的空引用。

public void IHaveNoControlOverWhereThisMethodIsCalled(object arg)
{
    if(arg == null)
        throw new ArgumentNullException("arg");    
}

private void TheOnlyCallersOfThisMethodComeFromMe(object arg)
{
    //I should do all my public parameter checking upstream and throw errors
    //at the public entry point only.
    if(arg == null)
         return;

}

在您的事件处理程序的特定情况下:

private void EmployeeMouseHoverToolTip(object sender, EventArgs e)
{
    var txtSender = sender as C1TextBox;
    if(txtSender == null) return;

    var sUserIdentifier = txtSender.Text;
    var userIdentifier = Guid.Empty;
    if (Utilities.IsGuid(sUserIdentifier))
    {
        userIdentifier = new Guid(sUserIdentifier);
    }

    var toolTipText = Utilities.UserIdentifierToName(userIdentifier);
    c1SuperTooltip.SetToolTip(sender as C1TextBox, toolTipText);
}

我没有为私有方法处理null,因为我总是确保不会将null值发送到我的私有方法。 如果出现问题并且null已经传递给私有方法,那么异常将从应该抛出,我将知道我做错了什么。 如果你总是检查私有方法的空值,你可能会在运行时跳过一些逻辑错误,你永远不会知道你的代码中有一个,直到它在生产中遇到你。

根据ILDASM的说法,我会说选项#2(略微)更高效:

码:

class Program
{
    static void Main(string[] args)
    {
        Method1(null);
        Method2(null);
    }

    static void Method1(object sender)
    {
        if (sender == null)
            return;

        for (int x = 0; x < 100; x++)
        {
            Console.WriteLine(x.ToString());
        }
    }

    static void Method2(object sender)
    {
        if (sender != null)
        {
            for (int x = 0; x < 100; x++)
            {
                Console.WriteLine(x.ToString());
            }
        }
    }
}

方法1的ILDASM:

.method private hidebysig static void  Method1(object sender) cil managed
{
  // Code size       47 (0x2f)
  .maxstack  2
  .locals init ([0] int32 x,
           [1] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldnull
  IL_0003:  ceq
  IL_0005:  ldc.i4.0
  IL_0006:  ceq
  IL_0008:  stloc.1
  IL_0009:  ldloc.1
  IL_000a:  brtrue.s   IL_000e
  IL_000c:  br.s       IL_002e
  IL_000e:  ldc.i4.0
  IL_000f:  stloc.0
  IL_0010:  br.s       IL_0025
  IL_0012:  nop
  IL_0013:  ldloca.s   x
  IL_0015:  call       instance string [mscorlib]System.Int32::ToString()
  IL_001a:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_001f:  nop
  IL_0020:  nop
  IL_0021:  ldloc.0
  IL_0022:  ldc.i4.1
  IL_0023:  add
  IL_0024:  stloc.0
  IL_0025:  ldloc.0
  IL_0026:  ldc.i4.s   100
  IL_0028:  clt
  IL_002a:  stloc.1
  IL_002b:  ldloc.1
  IL_002c:  brtrue.s   IL_0012
  IL_002e:  ret
} // end of method Program::Method1

方法2的ILDASM:

.method private hidebysig static void  Method2(object sender) cil managed
{
  // Code size       44 (0x2c)
  .maxstack  2
  .locals init ([0] int32 x,
           [1] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldnull
  IL_0003:  ceq
  IL_0005:  stloc.1
  IL_0006:  ldloc.1
  IL_0007:  brtrue.s   IL_002b
  IL_0009:  nop
  IL_000a:  ldc.i4.0
  IL_000b:  stloc.0
  IL_000c:  br.s       IL_0021
  IL_000e:  nop
  IL_000f:  ldloca.s   x
  IL_0011:  call       instance string [mscorlib]System.Int32::ToString()
  IL_0016:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_001b:  nop
  IL_001c:  nop
  IL_001d:  ldloc.0
  IL_001e:  ldc.i4.1
  IL_001f:  add
  IL_0020:  stloc.0
  IL_0021:  ldloc.0
  IL_0022:  ldc.i4.s   100
  IL_0024:  clt
  IL_0026:  stloc.1
  IL_0027:  ldloc.1
  IL_0028:  brtrue.s   IL_000e
  IL_002a:  nop
  IL_002b:  ret
} // end of method Program::Method2

与@Konrad类似,但我喜欢在代码中添加异常的想法

if( sender != null )
{
    // Code goes here
    ....
} else
    throw new ArgumentNullExcpetion("sender");

所以我投票给选项# 2

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM