孙展波:实现.NET无限潜力

Realize .NET Potential (中文版)
随笔 - 430, 评论 - 12203, 引用 - 54

导航

关于

贴子以"现状"提供且没有任何担保也没有授予任何权利。
计数器(2005/12/8起).点击阅读我的Blog In English

标签

每月存档

最新留言

广告

设计指导: struct结构总包含其缺省构造函数

在.NET框架3.0或者3.5版本下,下面的代码会在GetHashCode时抛出NullReferenceException:

System.ComponentModel.SortDescription description = new System.ComponentModel.SortDescription();
int hash = description.GetHashCode();

设计指导:不要抛出NullReferenceException中,我们提到此准则至少包含两个方面的意思:

  1. 代码中决不要显性地 throw new NullReferenceException
  2. 代码中要有充分的错误检查,避免由CLR抛出NullReferenceException.

上面代码的问题是由于第一点,还是第二点呢?我们只有看到源代码才能确定。

在源代码中,我们看到对于这个public struct SortDescription,程序员定义了一个构造函数:

        public SortDescription(string propertyName, ListSortDirection direction)

        {

            if (direction != ListSortDirection.Ascending && direction != ListSortDirection.Descending)

                throw new InvalidEnumArgumentException("direction", (int)direction, typeof(ListSortDirection));

 

            _propertyName = propertyName;

            _direction = direction;

            _sealed = false;

        }

而GetHashCode的实现如下:

        public override int GetHashCode()

        {

            return unchecked(PropertyName.GetHashCode() + Direction.GetHashCode());

        }

其中PropertyName简单返回_propertyName. 您一定注意到,构造函数没有对propertyName是否为null做检查。 这是会导致GetHashCode时的NullReferenceException.

但是,我们最初的代码实际上使用的是struct结构的缺省构造函数。和class不同,即使程序员显式地提供了一个非缺省构造函数,struct仍然保有缺省构造函数。在设计库函数时,我们需要留意这些区别。

------ ------
值此Blog 400篇之际,MSR Asis特制对联一幅, 以谢读者:

Chun Lian 400

posted on 2008-02-18 09:10:00 by zhanbos  评论(2) 阅读(5352)

设计指导:不要抛出NullReferenceException

一个类(或者一个结构)封装了一些相关的属性和方法。在使用这些属性和方法的时候,设计指导要求从不会得到NullReferenceException。这至少包含两个方面的意思:

  1. 代码中决不要显性地 throw new NullReferenceException
  2. 代码中要有充分的错误检查,避免由CLR抛出NullReferenceException.

如果某个函数的一个参数不能为null,函数代码需要检查输入是否合法。如果输入是null,代码需要抛出的是ArgumentNullException

即便是来自微软.NET框架库中的代码也未能100%遵守设计指导,将如下一篇Blog所示。

posted on 2008-01-21 12:04:00 by zhanbos  评论(6) 阅读(7235)

与时俱进的设计指导 (1):ArgumentNullException Helper

严格的说,一个方法若抛出ArgumentNullException,其paramName值应该是这个方法自身定义的参数名称(是参数的名字而非其值)。在实践中,越来越多的代码将对参数值的一些检查交给若干Helper方法来完成。比如:

public static void CheckArgumentNotNull(object parameter, string parameterName)
{
    if (null == parameter)
    {
        throw new ArgumentNullException(parameterName, StringResource.Get("ParameterCannotBeNULL"));
    }
}

调用方法会把参数实例和参数名一起传给CheckArgumentNotNull,比如CheckArgumentNotNull(parent, "parent");注意到2者在字面上的区别只是有没有引号。

使用这样的Helper方法,使代码稍微简化了一些。而代码中出现的parameterName值也几乎不可能是"parameter"。这虽然违反了抛出ArgumentNullException的最初的设计指导,鉴于这样的使用越来越多,我们也就只能接受这样的实践。是为与时俱进的设计指导之一。

posted on 2006-05-06 14:32:00 by zhanbos  评论(16) 阅读(7201)

Coding Style: 0x4 还是 1 << 2 ?

对于使用FlagsAttribute的枚举型的定义,见得较多的是直接给出值,如:

        [Flags]

        public enum TechEdCities

        {

            None = 0x0,

            Guangzhou = 0x1,

            Shanghai = 0x2,

            Beijing = 0x4

        }

最近也看到有人这样写:

        [Flags]

        public enum TechEdCities

        {

            None = 0,

            Guangzhou = 1 << 0,

            Shanghai = 1 << 1,

            Beijing = 1 << 2

        }

编译出来的结果是一样的,但是您更愿意书写/阅读哪一种写法呢,有原因么?

posted on 2005-11-01 04:42:00 by zhanbos  评论(25) 阅读(4408)

XAML之EventTrigger的一个演示和两个Bug

以下为使用EventTrigger的一个XAML示例:当您把鼠标移入和移出Button时候其背景颜色会有改变。

<StackPanel Background="white" xmlns="http://schemas.microsoft.com/winfx/avalon/2005" xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005" Tag="DP0001"> <Button Name="FirstBtn" Background="LightBlue" Content="实现WPF无限潜力"> <Button.Triggers> <EventTrigger RoutedEvent="Button.MouseEnter"> <BeginStoryboard Name="story"> <BeginStoryboard.Storyboard> <Storyboard TargetProperty="Background.Color" > <ColorAnimation To="Green" Duration="0:0:2" AutoReverse="True" FillBehavior="Stop"/> </Storyboard> </BeginStoryboard.Storyboard> </BeginStoryboard> </EventTrigger> <EventTrigger RoutedEvent="Button.MouseLeave"> <SeekStoryboard BeginStoryboardName="story" Offset="0:0:1"> </SeekStoryboard> </EventTrigger> </Button.Triggers> </Button> </StackPanel>

但是在九月CTP的版本之中,这个XAML暴露出了2个Bug。其中一个是在功能实现上的,而另外一个则是由于对.NET的例外函数的使用不正确而造成。

第一个Bug,您需要对FillBehavior的作用有正确了解。第二个Bug,请将<SeekStoryboard BeginStoryboardName="story" Offset="0:0:1">
改成
<SeekStoryboard BeginStoryboardName="story" Offset="0:0:10">
后看您能否一眼看出吧。

当然,这些问题已经被改正。如果您在使用CTP若遇到其他功能或者使用上的Bug,可以通过留言告知。

posted on 2005-10-03 16:16:00 by zhanbos  评论(18) 阅读(4823)

sealed能提高性能

有人问使用sealed声明密封类(sealed class)对于提高性能有帮助么?答案是肯定的。如果根据设计一个类可以被sealed,则我们应该这样做。

 

比如说,如果基类(base classB中定义了虚函数,而sealed class SB类衍生。对于一个类型为S的名为s的变量调用虚函数的代码,编译器可以确信s一定是类型为S的。但是如果类S实际上没有被sealed,则这个变量s可能是类S的衍生类的实例而这个衍生类同时又重写(override)了该虚函数。这时为了正确性,编译器必须以虚函数调用的方式执行该代码。这比直接执行的成本要高。

 

另外一个例子是attribute属性类。有一个FxCop的规则(Avoid unsealed attributes)专门检查定义的属性类是不是sealed。除了上面谈及的原因,还特别提到Attribute.GetCustomAttributeAPI. 其解释如下:The .NET Framework class library provides methods for retrieving custom attributes. These methods search the attribute inheritance hierarchy by default; for example System.Attribute.GetCustomAttribute searches for the specified attribute type, or any attribute type that extends the specified attribute type. Sealing the attribute eliminates the search through the inheritance hierarchy, and can improve performance.

 

关于FxCop的讨论,请参看定制FxCop规则示例之一

 

贴子以"现状"提供且没有任何担保也没有授予任何权利

posted on 2005-05-29 18:43:00 by zhanbos  评论(11) 阅读(9139)

回答有关FxCop的2个问题

有朋友来函,肯定了FxCop的价值,也提出了问题。信中写道:

 

我尝试在公司推广FxCop来做代码规范(我想.NET的开发中在这方面应该不会有更好的工具了),但是有一些问题:

 

1、这个项目是不是开源的(我在gotdotnet没看到有源码下载)?我想自己改成中文版的(毕竟大部分开发者的英文能力一般),或者已经有人在做了?

 

2、我想汉化规则的名字,比如把“DoNotDeclareVisibleInstanceFields”显示为“不要定义公开的字段成员”,我没有找到可以改变的地方(我以为会有个XML文件可配置)

 

关于您的第一个问题,FxCop不是开源项目。但是我们注意到FxCop将会整合到Visual Studio 2005之中,而Visual Studio将会汉化的。所以虽然FxCop作为一个独立的产品还没有汉化的时间表,与VS2005整合的代码分析功能将会汉化。

 

第二个问题,您提到的规则名称就是在定制FxCop规则示例之一RuleInfo.xml里面每个rule下的内容。这个XML文件是作为Embedded ResourceBuildrule assemblies之中的。如果您自己编写规则,可以在XML中使用中文字符。

 

最后提及一点:FxCop是基于IL之上的,它不可能检查代码规范的每个方面。比如说,源代码格式,Assert的使用等。

 

每个公司的规范都有其特殊性,所以定制自己的FxCop规则是必不可少的。请关注在本Blog以后发布的示例。

 

贴子以"现状"提供且没有任何担保也没有授予任何权利

posted on 2005-04-02 12:02:00 by zhanbos  评论(11) 阅读(3245)

定制FxCop规则示例之一:AvoidICloneableImplementation

2年前的Tech Ed China上就有过FxCop的介绍,当时译为“框架警察”。关于FxCop的介绍和若干规则示例,可以参考John Robbins所写的Bad Code? FxCop to the RescueThree Vital FXCop Rules文章。本系列将由浅入深的解释定制FxCop的常用方式和技巧。

 

设计新类时要避免实现ICloneableBlog中仅仅解释了其原因。对于Design Guideline的实施,可以经常得到FxCop的帮助。既然FxCop自带的规则还没有包括对这一设计指导的检查,让我们自己来写一个这样的规则。全部步骤如下。使用的是.NET框架1.1VS2003FxCop 1.312版本,注意FxCop规则编程现在并没有得到官方支持。如果以后版本API发生变化,我会对示例程序有选择的更新。

  1. 下载FxCop 1.312: http://www.gotdotnet.com/team/fxcop/ 选择FxCop for .NET 1.1.
  2. 安装FxCop 1.312, 其缺省安装在“%ProgramFiles%\Microsoft FxCop 1.312”文件夹下。注意其中包括的FxCopSdk.dllMicrosoft.Cci.DLL2个程序集(assembly)。我们的规则项目需要引用它们。
  3. 启动FxCop, 留意到在Rules Tab下所有预先提供的Rules.
  4. 设计新类时要避免实现ICloneable中提供的测试用例编译成为一个Class library起名为FxCopRuleTests
  5. 通过菜单,工具栏或者Ctrl+Shift+A以增加Target,即这些Rule将检查的程序集。添加FxCopRuleTests
  6. 通过菜单,工具栏或者F5开始分析。结果有7violation messages,但是没有设计新类时要避免实现ICloneable相关的消息。我们现在就开始写这样的一个FxCop Rule.
  7. 使用VS2003创建新的Class Library项目起名CustomFxCopRules.
  8. 删除IDE创建的Class1.csAssemblyInfo.cs.
  9. 添加对FxCopSdk.dllMicrosoft.Cci.DLL的引用
  10. 增加一个类文件叫做AvoidICloneableImplementation.cs.
  11. 添加一个命名为RuleInfo.xmlXML文件,设置其Build ActionEmbedded Resource.
  12. 使用如下的代码,实现这一简单的FxCop Rule,编译后通过菜单,工具栏或者Ctrl+R加入FxCopRule之中,再运行FxCop看到正确结果。(提示,可以uncheck所有FxCop自带规则以减少干扰。

 

这是RuleInfo.xml

<?xml version="1.0" encoding="utf-8" ?>

<Rules FriendlyName="Custom FxCop Rules">

  <Rule TypeName="AvoidICloneableImplementation" Category="ZhanboBlog.Demo" CheckId="ZB001">

    <Name>Avoid ICloneable Implementation</Name>

    <Description>ICloneable could be used to return either deep copy or shallow copy, and is therefore not useful.</Description>

    <Url>http://blog.joycode.com/zhanbos/archive/2005/03/13/45707.aspx</Url>

    <Resolution>Type '{0}' Implements ICloneable, which should be avoided.</Resolution>

    <Email />

    <MessageLevel Certainty="99">Error</MessageLevel>

    <FixCategories>Breaking</FixCategories>

    <Owner />

  </Rule>

</Rules>

 

这是AvoidICloneableImplementation.cs

using System;

using Microsoft.Cci;

using Microsoft.Tools.FxCop.Sdk;

using Microsoft.Tools.FxCop.Sdk.Introspection;

 

namespace ZhanboBlog.Demo.CustomRules

{

  /// <summary>

  /// Do not implement ICloneable interface

  /// </summary>

  public class AvoidICloneableImplementation : BaseIntrospectionRule

  {

    public AvoidICloneableImplementation() :

      base("AvoidICloneableImplementation", "ZhanboBlog.Demo.CustomRules.RuleInfo", typeof(AvoidICloneableImplementation).Assembly)

    {

    }

 

    public override ProblemCollection Check(TypeNode type)

    {

      if (DoesImplementICloneable(type.Interfaces))

      {

        Resolution resolution = GetResolution(RuleUtilities.Format(type));

        Problem newProblem = new Problem(resolution, type);

        Problems.Add(newProblem);

        return Problems;

      }

      return null;

    }

 

    private bool DoesImplementICloneable(InterfaceList interfaceList)

    {

      for(int i = 0; i < interfaceList.Length; i++)

      {

        if (interfaceList[i] == SystemTypes.ICloneable)

        {

          return true;

        }

      }

      return false;

    }

  }

}

 

如果您对某些代码有疑惑,可以留下评论我会在下一次Blog中提供必须的解释。通过实践会加深认识。您有什么希望增加FxCop规则检查的情形也欢迎提出,作为以后FxCop示例的参考。

 

贴子以"现状"提供且没有任何担保也没有授予任何权利

posted on 2005-03-13 11:41:00 by zhanbos  评论(16) 阅读(10391)

设计新类时要避免实现ICloneable

ICloneable只定义了一个Clone的方法既可以实现为深拷贝(deep copy)也可以实现为浅拷贝shallow copy。因为这种二意性用户不能够依赖Icloneable得知Clone返回的对象是深拷贝还是浅拷贝。这是在设计ICloneable时的一个缺陷。有鉴于此,在设计新类的时候,要避免实现ICloneable

 

以下是一个不正确的例子。

 

 

using System;

 

namespace ZhanboBlog.Demo.FxCopRuleTests

{

  public class TestAvoidICloneable : ICloneable

  {

    public TestAvoidICloneable()

    {

      secretBlob = System.DateTime.Now.ToShortTimeString();

    }

 

    public object Clone()

    {

      TestAvoidICloneable copy = new TestAvoidICloneable();

      copy.secretBlob = this.SecretBlob;

      return copy;

    }

 

    public string SecretBlob

    {

      get

      {

        return secretBlob;

      }

    }

    private string secretBlob;

  }

}

 

贴子以"现状"提供且没有任何担保也没有授予任何权利

posted on 2005-03-13 08:59:00 by zhanbos  评论(17) 阅读(5163)

解答:设计指导的自相矛盾之处

一周前的有奖问答的题目是自相矛盾的设计指导前后的矛盾之处在于TypeConverter类在ConvertToConvertFrom无法执行的时候抛出NotSupportedException是不符合设计指导的要求的无法Convert的情形是对于某些输入而言的。最合适的异常是在比较InvalidOperationException NotSupportedException之后我提到的“不要忘记ArgumentException及其子类

 

当然现在就只能将错就错,将TypeConverter单列出作为特例。因为不是每个程序员都充分了解和注意所有的设计指导,未来的新增加的API也未必能100%与设计指导保持一致。如果您在BetaASP.NET 2.0, WinFrom2.0, Avalon 1.0, Indigo 1.0发现使用不妥之处,请及时指出,以利于改进。

 

贴子以"现状"提供且没有任何担保也没有授予任何权利

posted on 2004-11-26 17:38:00 by zhanbos  评论(2) 阅读(2787)

有奖问答:自相矛盾的设计指导?

在题为“区分使用InvalidOperationExceptionNotSupportedException”的Blog里面我首先提到如何区分使用 InvalidOperationException NotSupportedException然后归纳了在FCL 1.x之中InvalidOperationException NotSupportedException的使用。

 

前后是不是有自相矛盾之处呢?请回答并有解释说明,第一个给出合适回答的朋友将得到Alienware Mousepad作为小纪念品。要在12月下旬才可以寄出。可能是我也可能请Grace代为发送。

 

贴子以"现状"提供且没有任何担保也没有授予任何权利

posted on 2004-11-19 13:24:00 by zhanbos  评论(15) 阅读(5066)

区分使用InvalidOperationException和NotSupportedException

对于不同的错误情形,库函数需要选择合适的Exception的类别和Exception Message。如何区分使用 InvalidOperationException NotSupportedException呢?

 

NotSupportedException: 如果API总是会失败无论其input(如参数)的值是什么。

 

InvalidOperationException: 如果API在当前对象所处的状态(不管输入是什么)下会失败,但是当对象的状态改变时能成功。

 

当然不要忘记ArgumentException及其子类适用于API的当前参数值不合法的情形。

 

FCL 1.x之中,NotSupportedException的使用主要用以下3种情形:

 

  1. 基类对一些函数没有实现而期待其子类去实现。但是子类可能只实现部分这样的函数,而对其他不支持的函数抛出NotSupportedException.
  2. System.IO的类在试图read, seek,write并不支持此功能的stream时抛出NotSupportedException.
  3. TypeConverter类在ConvertToConvertFrom无法执行的时候抛出NotSupportedException.

 

InvalidOperationException的使用则要广泛多了。

 

FCL 2.0WinFX1.0中,我希望InvalidOperationExceptionNotSupportedException的区别能够继续体现出来。如果您在BetaASP.NET 2.0, WinFrom2.0, Avalon 1.0, Indigo 1.0发现使用不妥之处,请及时指出,以利于改进。常见的错误是在需要使用InvalidOperationException之处使用了NotSupportedException.

 

贴子以"现状"提供且没有任何担保也没有授予任何权利

posted on 2004-11-18 16:20:00 by zhanbos  评论(6) 阅读(3196)

重载,重写和实现接口时保持相同参数名

如果已经定义了一个interface如下:

  public interface IExample

  {

    void ShowExampe(string example);

  }

 

有一个类是这样实现此interface

  public class DetailedExample : IExample

  {

    public void ShowExampe(string detailedInfo)

    {

      //implementation detail

    }

  }

 

有什么问题么?编译是可以通过的,但是微软的.NET设计指导要求在实现ShowExample的时候不改变参数名,所以detailedInfo应该命名为example.

 

Guideline: Do be consistent in naming parameters when overloading, overriding and implementing interfaces.  This discipline increases the developer perception of connection and interrelation between the methods.  (在重载,重写和实现接口时需要保持参数名的一致性。这一原则增加了程序员对于方法之间的联系和相互关系的理解。)

 

贴子以"现状"提供且没有任何担保也没有授予任何权利

posted on 2004-11-12 10:29:00 by zhanbos  评论(7) 阅读(3231)

Powered by: Joycode.MVC引擎 0.5.2.0