银河

SKYIV STUDIO

  博客园 :: 首页 ::  ::  :: 订阅 订阅 :: 管理 ::
  105 随笔 :: 2 文章 :: 753 评论 :: 22 引用

猜猜看以下程序的输出是什么?

using System;
using System.Windows.Forms;

namespace Skyiv.Ben.Test
{
    
sealed class MainTest
    
{
        
static void Main()
        
{
            Label lblOut 
= new Label();
            lblOut.Text 
= null;
            Console.WriteLine(
"{0} {1}", lblOut.Text == null, lblOut.Text == "");
        }

    }

}

该程序的输出是: False True,而不是: True False。也就是说,你将一个null赋值给lblOut.Text,而lblOut.Text的值是""(即string.Empty),而不是null。lblOut.Text是一个属性,实际上,按C#语法,给属性赋值是调用它的set方法,而获取属性的值是调用它的get方法,这两者不必一致,也就是说,你给属性赋一个值,然后再读该属性的值,取到的值就有可能不是你刚刚赋给它的值了。编程时如不小心,就有可能出现BUG。我以前写程序时就出现过这样的错误:
lblOut.Text = GetAccount();
if (lblOut.Text == null) lblOut.Text = "无此账号";
结果这个if语句永远不会取真值。
posted on 2005-09-22 20:59 银河 阅读(2279) 评论(18)  编辑 收藏 所属分类: C# Base

评论

嘿嘿 ,明白点心动西
  回复  引用    

#2楼  2005-09-22 21:43 怀沙      
呵呵。。。这一点的确是容易搞错
不过在实际运用中。。很少有人会写lblOut.Text = null;这样的语句。
  回复  引用  查看    

#3楼 [楼主] 2005-09-22 21:59 银河      
@怀沙:
如我在正文中提到的, 在实际运用中, 我是写: lblOut.Text = GetAccount();
而 GetAccount() 方法在账号不存在时就返回null, 导致接下来的if语句失败.
  回复  引用  查看    

#4楼 [楼主] 2005-09-22 22:01 银河      
@ocean2000:
> 嘿嘿 ,明白点心动西
你是不是想说: 明白点新东西?
  回复  引用  查看    

#5楼  2005-09-22 22:06 negy [未注册用户]
碰到过这个问题
  回复  引用    

#6楼  2005-09-23 09:28 yicone      
很实际,谢谢
  回复  引用  查看    

#7楼  2005-09-23 09:28 疾风      
似乎GetAccount()中,当帐号不存在时抛出异常是更好的处理办法
  回复  引用  查看    

#8楼  2005-09-23 09:29 wny      
看看源码就知道原因了:
public virtual string Text
{
      get
      {
            ......
             if (this.text != null)
            {
                  return this.text;
            }
            return "";
      }
      set
      {
            if (value == null)
            {
                  value = "";
            }
            ......
      }
}

  回复  引用  查看    


呵呵 Reflector.exe 一下就知道了

public virtual string get_Text()
{
if (!this.GetStyle(ControlStyles.CacheText))
{
return this.WindowText;
}
if (this.text != null)
{
return this.text;
}
return "";
}

  回复  引用  查看    

#10楼  2005-09-23 09:58 杨波 [未注册用户]
微软所有字符串属性都有对null处理。再说了,那有你这样写代码的。
通用写法:
string str = GetAccount();
//1
lblOut.Text = (str == null) ? "无此账号" : str;
//2
if(str == null)
{
lblOut.Text = "无此账号";
}
else
{
lblOut.Text = str;
}

  回复  引用    

#11楼  2005-09-23 10:57 birdshome      
在Fx 1.1里面判断字符串是否为空,以该是:if ( str != null && str.Length > 0 )
在Fx 2.0里面判断字符串是否为空,以该是:if ( !String.IsNullOrEmpty(str) )
  回复  引用  查看    

#12楼 [楼主] 2005-09-23 14:13 银河      
@疾风
> 似乎GetAccount()中,当帐号不存在时抛出异常是更好的处理办法
这是个设计权衡的问题,有时候是应该抛出异常,但有时候返回null值会更好些,要看大多数情况下的调用者的应用场合。如果在这个例子中,GetAccount()当账号不存在时抛出异常的话,调用者就必须使用try...catch语句块了,在这个场合来说,不是很好的设计。
  回复  引用  查看    

#13楼 [楼主] 2005-09-23 14:18 银河      
@杨波
> 再说了,那有你这样写代码的。
如果不是Text属性把null值转换为""值,为什么不能这样写代码?至少,可以少使用一个临时变量。我提出这个问题,只不过是给大家提个醒,省得出现这种BUG。
  回复  引用  查看    

#14楼  2005-09-23 15:56 小陆      
禁止使用“==”判断两个字符串是否相同。
“==”只能判断两个字符串的“引用”是否相同。
要使用equals方法,如果不判断字符串是否为null,equals自然会出现异常,不存在隐藏的bug。

  回复  引用  查看    

#15楼 [楼主] 2005-09-23 16:10 银河      
@小陆
> 禁止使用“==”判断两个字符串是否相同。
> “==”只能判断两个字符串的“引用”是否相同。
完全可以使用“==”判断两个字符串是否相同。因为System.String类已经重载了“==”方法,从而判断两个字符串的内容是否相同,而不是其“引用”是否相同。如果不是这样的话,我在正文中的程序也就不可能输出: False True 了,事实证明,lblOut.Text == "" 的值是True,而不是False。如果“==”是比较“引用”是否相同的话,lblOut.Text == ""的值应该是False。
  回复  引用  查看    

#16楼  2005-09-23 21:13 yuxs [未注册用户]
@小陆
由于string是使用量非常大的数据类型,所以为了编码方便,string在C#/.NET中进行了特别处理,可以看作是一个能够当作值类型使用的引用类型
  回复  引用    

#17楼 [楼主] 2005-09-29 11:39 银河      
@小陆
> 禁止使用“==”判断两个字符串是否相同。
> “==”只能判断两个字符串的“引用”是否相同。
补充说明如下:
《.NET本质论-第1卷:公共语言运行库》,Don Box, Chris Sells著,张晓坤译,中国电力出版社,2004年4月第1版
“第5章 实例”,“相等与同一”小节,pp.127-128:
  如果不考虑编程语言的特殊性,则很难说清楚同一性和相等性。在C++和C#中,标准的比较操作符是==和!=。当这些操作符被应用在基本数据类型上时,它们只是简单地发射直接比较这两个值的CIL指令。当被应用在对象引用上时,这些操作符发射的CIL指令原则上等价于调用System.Object.ReferenceEquals方法。然而,C++和C#都支持操作符重载,这意味首一个特定的类型可能将==(和!=)操作符映射到任意代码。典型的例子就是System.String类型。System.String类型重载了这些操作符,使之调用Equals方法。于是,对于字符串比较,就能采用更为直观的相等比较。一般来说,重写Equals方法的类型应该考虑重载==和!=操作符,尤其当类型是(或者其行为像)值类型时。

  回复  引用  查看    

#18楼  2005-11-03 10:04 小陆      
==和equals是决不相同的,不管他重载不重载。
string s = null;
string n = null;
这时候==返回true,而equals会出现异常。
null从逻辑上表示“未知”,两个“未知”是不应该相同的。使用==来判断两个字符串,可能会掩盖程序中的错误。
写程序如果有错误,最好是在编译的时候出错,其次是在运行的时候迅速出错。这个时候的错误出现的越多,程序最后就是越强壮的。
  回复  引用  查看    

#19楼 [楼主] 2005-11-03 11:47 银河      
@小陆
小陆的评论之一禁止使用“==”判断两个字符串是否相同。
“==”只能判断两个字符串的“引用”是否相同。
要使用equals方法,如果不判断字符串是否为null,equals自然会出现异常,不存在隐藏的bug。
小陆的评论之二==和equals是决不相同的,不管他重载不重载。
string s = null;
string n = null;
这时候==返回true,而equals会出现异常。
null从逻辑上表示“未知”,两个“未知”是不应该相同的。使用==来判断两个字符串,可能会掩盖程序中的错误。
写程序如果有错误,最好是在编译的时候出错,其次是在运行的时候迅速出错。这个时候的错误出现的越多,程序最后就是越强壮的。
首先,你在评论中提到的“equals”都应该改为“Equals”,因为C#是区分大小字母的。
其次,我不同意你“评论之一”中的如下观点:
> 禁止使用“==”判断两个字符串是否相同。
> “==”只能判断两个字符串的“引用”是否相同。
理由是:
Don Box, Chris Sells 说当被应用在对象引用上时,这些操作符(标准的比较操作符==和!=)发射的CIL指令原则上等价于调用System.Object.ReferenceEquals方法。然而,C++和C#都支持操作符重载,这意味首一个特定的类型可能将==(和!=)操作符映射到任意代码。典型的例子就是System.String类型。System.String类型重载了这些操作符,使之调用Equals方法。于是,对于字符串比较,就能采用更为直观的相等比较一般来说,重写Equals方法的类型应该考虑重载==和!=操作符,尤其当类型是(或者其行为像)值类型时。
而对于你的“评论之二”中的观点基本上我都赞同。
但使用“==”来判断两个字符串是否相同是非常方便和直观的(在实践中也是被广泛采用的),而且实际上和使用“Equals”方法差别非常小(其差别就是你在“评论之二”中提到的),我认为在大多数情况上不会造成影响。
我们用“Lutz Roeder's .NET Reflector”来看一下 System.String 的源代码:
System.String.op_Equality(String, String) : Booleanpublic static bool operator ==(string a, string b)
{
  return string.Equals(a, b);
}
System.String.Equals(String, String) : Boolean public static bool Equals(string a, string b)
{
  if (a == b) // IL Code: bne.un.s L_0006
  {
    return true;
  }
  if ((a != null) && (b != null))
  {
    return string.EqualsHelper(a, b);
  }
  return false;
}
System.String.Equals(String) : Boolean[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public bool Equals(string value)
{
  if ((value == null) && (this != null))
  {
    return false;
  }
  return string.EqualsHelper(this, value);
}
System.String.EqualsHelper(String, String) : Boolean [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
private static unsafe bool EqualsHelper(string strA, string strB)
{
  int num1 = strA.Length;
  if (num1 != strB.Length)
  {
    return false;
  }
  fixed (char* local1 = strA)
  {
    char* chPtr1 = local1;
    fixed (char* local2 = strB)
    {
      char* chPtr2 = local2;
      char* chPtr3 = chPtr1;
      char* chPtr4 = chPtr2;
      while (num1 >= 10)
      {
        if ((((*(((int*) chPtr3)) != *(((int*) chPtr4))) || (*(((int*) (chPtr3 + 2))) != *(((int*) (chPtr4 + 2))))) || ((*(((int*) (chPtr3 + 4))) != *(((int*) (chPtr4 + 4)))) || (*(((int*) (chPtr3 + 6))) != *(((int*) (chPtr4 + 6)))))) || (*(((int*) (chPtr3 + 8))) != *(((int*) (chPtr4 + 8)))))
        {
          break;
        }
        chPtr3 += 10;
        chPtr4 += 10;
        num1 -= 10;
      }
      while (num1 > 0)
      {
        if (*(((int*) chPtr3)) != *(((int*) chPtr4)))
        {
          break;
        }
        chPtr3 += 2;
        chPtr4 += 2;
        num1 -= 2;
      }
      return (num1 <= 0);
    }
  }
}
可见它们最终都是调用“System.String.EqualsHelper(String, String) : Boolean”方法来判断两个字符串是否相同。
  回复  引用  查看    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2005-10-07 17:52 编辑过
"五向定位"职业成长路线公开课(上海、南京、大连)
Google站内搜索


相关链接: