Shuhari

2009年12月21日

无限期停止更新

出于大家都心知肚明的原因,本人对国内的网络环境已经不抱希望。从今日始,博客内容无限期停止更新,今后全部活动和内容转向境外服务器。

 

祝某些人和某些部门在大中华局域网里玩得开心。

posted @ 2009-12-21 11:06 Shuhari 阅读(102) 评论(0) 编辑

2009年12月12日

C#编译器是如何判定某个变量没有使用过的?

这是我们某个组员在编程过程中提出的疑问。因为这个编译错误很容易避免,所以我一直也没有仔细想过这个问题,直到看过他的代码后才意识到,此问题并不是那么简单的。

先看看这段代码:

代码
class Program
{
    
static void Main(string[] args)
    {
        
byte[] buf = new byte[1024];
        T t 
= new T();
        
string str = "1234";
        
int n = 1234;
        
int? nn = 1234;
        DateTime dt 
= DateTime.Now;
        
object o = 1234;

        Console.WriteLine(
"finish");
    }
}

class T { }

 

你觉得这段代码里有几个变量没有使用过呢?

如果从程序员的角度来看,答案应该是所有变量都没有使用过。但编译器给出的结果却有点违反直觉:

变量“str”已赋值,但其值从未使用过
变量“n”已赋值,但其值从未使用过
变量“nn”已赋值,但其值从未使用过


奇怪的地方在于,虽然所有变量都是用同样的方式声明,但编译器却只认为其中一部分没有使用过。这是怎么回事呢?

我们一个一个来分析。首先看看数组,如果使用默认值的话,编译器给出的信息就不同了:

byte[] buf1 = null;     // 有警告
byte[] buf2 = new byte[1024]; // 没有警告



这个结果似乎表明,如果参数赋值为null,那么编译器并不会真的执行赋值,并且变量会当作没有使用过。用IL检查的结果也可以证明此说法:对第一行,编译器没有生成任何对应的语句;对第二条则使用了newattr指令来创建数组。


对于自定义的类:

T t1 = null;    // 有警告
T t2 = new T(); // 没有警告


这个结果应当是可以理解的(尽管可以理解,但我认为并不好,理由见后)。虽然我们并没有调用该类的任何方法,但是类的构造函数仍然可能执行某些操作,所以只要创建了一个类,编译器就会把它当作已经使用过的。

对于基本值类型,其表现和引用类型又有所不同,编译器并不把初始赋值当作对变量的使用:

int n1 = 0;  // 有警告
int n2 = 1234// 有警告
int? n3 = null// 有警告
int? n4 = 0// 有警告
int? n5 = 1234// 有警告


string从实现上来说应当算是引用类型,但表现上却更加类似于值类型,警告信息也和值类型相同。

对于稍微复杂一些的值类型,结果有点微妙:

DateTime dt1;   // 有警告
DateTime dt2 = new DateTime();  // 有警告
DateTime dt3 = new DateTime(2009,1,1);  // 没有警告
DateTime dt4 = DateTime.Now;  // 没有警告


这个结果有一点是需要注意的。尽管DateTime的默认构造函数和带参构造函数从用户角度看同样是构造函数,但在编译器的角度来看却是不一样的。用IL反编译也可以看出,如果调用默认构造函数的话,那么编译器调用的是initobj指令,而对带参构造函数调用的则是call ctor指令。此外,尽管从程序员的角度来看赋值代码的格式是完全相同的,但编译器却会根据所赋的值不同而采取不同的构造策略,这也是比较违反直觉的。

最后的结论比较遗憾,那就是C#的编译警告并不足以给予程序员足够的保护,特别是对于数组:
byte[] buf = new byte[1024];
如果仅构造这样一个数组而没有使用的话,那么编译器并不会给予程序员任何警告信息。

另外一个问题也是值得考虑的,声明一个类而不使用任何方法,比如仅仅
T t = new T()
这是合理的行为吗?编译器应该为此发出警告吗?
我个人的看法是,从使用的角度来说,这是不合理的,应当尽量避免,编译器发现此用法的话应该提出警告。如果确实有需要的话,可以通过编译指令或Attribute的方法来特别声明来避免警告信息。然而C#编译器的行为却是不发出警告,这一点我是不认同的。当然,我也希望大家提出自己的想法。

posted @ 2009-12-12 12:21 Shuhari 阅读(1487) 评论(9) 编辑

2009年11月13日

Silverlight校验框架的限制——基于异常的验证机制

我们在项目中使用了Silverlight 3中新增的校验机制。一开始,感觉很不错:可以用标注的方式声明验证逻辑,自动设置校验控件,自动验证数据——一切似乎很好。但是很快我发现,Silverlight的校验机制也是存在严重限制的。

 

对于不熟悉Silverlight校验机制的朋友,我可以在这里作一个简单的介绍。关键在于System.ComponentModel.DataAnnotations这个程序集,它提供了一些标记属性,你可以为实体添加这些标记,然后在编写实体读写方法的时候添加一些触发校验逻辑的桩代码,那么内置有数据校验功能的控件(比如Label和DataForm等)就能自动识别、并按照你设定的值来进行校验。

 

下面是从Pro Silverlight 3 for C#中摘抄的一段代码。即使不看手册,其中规定的验证逻辑是很容易看懂的:

[StringLength(25)]
[Display(Name 
= "Model Name", Description = "This is the retail product name.")]
public string ModelName
{
    get { return modelName; }
    set
    {
        ValidationContext context = new ValidationContext(thisnullnull);
        context.MemberName 
= "ModelNumber";
        Validator.ValidateProperty(value, context);
        modelName 
= value;
        OnPropertyChanged(
new PropertyChangedEventArgs("ModelName"));
    }
}

 

看起来很简单,而且我们使用的开头一段时间内运行得也相当不错,省去了很多手工校验的工作。直到有一天我们创建了某个新实体的时候,麻烦来了。

 

问题是这样的,项目需求要求我们保存某些客户信息,其中Email是必须填写的。实现此逻辑只要为属性加上一个Required标注即可。但问题在于,尽管Email是必须填写的,但我们却无法为它提供一个合理的默认值,所以开始的时候此属性是空字符串。另一方面,这个实体最初是从服务器端通过序列化得到的,而进行序列化和反序列化的时候也会调用Setter,从而调用校验逻辑,抛出异常——这是我们不希望的行为。虽然不希望,我们却不能去掉它,如果去掉的话,那么Silverlight的校验逻辑就不能工作了!

 

此问题的关键点在于,序列化的时候需要调用实体的Setter,界面绑定的时候也要调用Setter,但两种情况下需要的行为却是不同的。创建一个新实体的时候,其中某些属性有可能是无效的,但我们并不能因此阻止用户创建新对象,这时候是应当禁用验证逻辑的。 那么接下来的问题就是,实体的Setter中能不能识别到是在哪一种情况下调用的,从而打开或关闭验证呢?

 

开始我想到了Environment.StackTrace,根据调用堆栈来判断运行环境,应该可以识别出代码运行的场合。但是实验一下就发现:此路不通。StackTrace这个属性在Silverlight版本的CLR中是根本没有提供的,于是这个方向被堵死了。

 

有的组员提出,是否可以设置一个提示性的初始值,比如“<请填写>”?这个建议很快被否决了,因为要求用户来删掉无效的值再重新输入并不合理,也不友好。

然后又有人说,是否可以根据实体的Id来判断,如果是0则表示是新建的对象,不需要校验? 这也是不可行的,因为新建的对象在提交的时候同样需要校验。

当然还有一个办法是为界面绑定和数据传递分别生成两套实体,一套有数据校验,一套没有,然后写代码来在它们之间进行转换。但是想想也可以知道,这样工作量实在太大了,也增加了维护的难度。

 

最终我们采取了一个比较笨的办法:为实体添加一个IsUIBinding标志,一开始为false,在绑定到界面之前设置为true,提交服务器之前再复原为false。这样是可以解决问题了,不过程序员的负担就更重了——必须记住在合适的时候修改这个标记,否则程序就会出现bug。

 

这个结果让我对Silverlight验证框架感到有点遗憾。Silverlight的验证方法过于严格——一旦数据不合法,ValidationException就会抛出,于是所有后续代码都无法执行,如果运行环境没有做好处理此异常的准备的话,那么整个程序都会出错。而其他的场景——比如序列化的时候是没有办法处理此异常的,这大大限制了校验机制的应用场景。其实从设计上看,Silverlight使用了ValidationResult来收集校验失败信息,那么理论上讲,不使用异常,而根据ValidationResult收集的结果来判断也是完全可能的。但最终Silverlight还是采用了异常的方法。不过尽管有此遗憾,Silverlight的校验机制对于一般的数据验证还是不错的,目前我们也不太可能抛开它去完全实现一套自己的校验方法,只有在编程的时候多加注意了。

posted @ 2009-11-13 16:48 Shuhari 阅读(433) 评论(6) 编辑

2009年11月12日

Silverlight陷阱:XAML中不能使用自定义字典

我们知道,XAML中实际上是可以放置任何对象的,而系统将按照如下的规则管理嵌套的内容:

1. 如果对象实现了IList,那么嵌套内容将通过IList.Add添加到父对象;

2. 如果对象实现了IDictionary,并且元素用x:Key指定了键值,那么嵌套内容将通过IDictionary.Add添加到父对象;

3. 如果只有父对象用ContentPropertyAttribute声明了内容属性,那么嵌套内容将被赋值为到该属性。

 

我们在目前的项目中使用了很多XAML声明来减少编码量,但是在使用中我们发现,第2条对于Silverlight是不适用的,Silverlight的XAML只支持对Resources属性用字典方式来声明,对于自定义的字典内容,即使是ResourceDictionary也无法读取,否则运行时就会抛出异常。因为同样的方法在服务端已经普遍使用,所以我们把代码应用到Silverlight工程中的时候,根本没有想到这方面会出问题。从而花了很长时间、走了很多弯路去查找自己程序中的Bug,反复作了大量实验后,终于确定:这个问题来源于Silverlight和WPF读取XAML时的表现不同。

 

还是用代码来说明。首先我们看看在WPF中使用自定义字典是否可行:

 

public class BaseWindow : Window
{
    
public BaseWindow()
    {
        Dict 
= new MyDict();
    }

    
public MyDict Dict { getset; }
}

public class MyDict : Dictionary<string, Brush>
{
}

 然后在窗体中添加字典数据:

 

<local:BaseWindow x:Class="TestWPF2.Window1"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
    Title
="Window1" 
    xmlns:local
="clr-namespace:TestWPF2">

    
<local:BaseWindow.Dict>
            
<SolidColorBrush x:Key="1" Color="Blue" />
            
<SolidColorBrush x:Key="2" Color="Black" />
    
</local:BaseWindow.Dict>
</local:BaseWindow>

 最后检查一下声明的字典是否正确设置了:

 

public Window1()
{
    InitializeComponent();
    
this.Background = Dict["1"];
}

运行结果完全正确(如图) ,表明自定义字典在WPF中是可行的。

 

 

然后我们再如法炮制一个Silverlight工程,运行之,出现异常:


 AG_E_PARSER_BAD_PROPERTY_VALUE [Line: 10 Position: 42]Type: XamlParseExceptionStackTrace:   

位于 System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)  

位于 TestSL2.MainPage.InitializeComponent()  

位于 TestSL2.MainPage..ctor()  

位于 TestSL2.App.App_Startup(Object sender, StartupEventArgs e)  

位于 System.Windows.CoreInvokeHandler.InvokeEventHandler(Int32 typeIndex, Delegate handlerDelegate, Object sender, Object args)  

位于 MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, String eventName)

 

此实验可以说明WPF和Silverlight 在处理XAML时候的不同行为。你可以把MyDict换成ResourceDictionary试试,结果也是一样的。

 

为了绕过这个问题,我们不得不在Silverlight工程中去掉字典,把内容重新组织成列表,然后在程序中将内容重新组织成字典——等于服务端已经调试好的方法在客户端又重写了一遍。非常恼人,但是没有办法。。

 

这个问题告诉我们,尽管Silverlight 源出WPF,但处理细节上还是存在微妙的差别。将WPF的经验应用到Silverlight上,不见得会奏效。最好是先作一些小实验,确信方法是可行的,再添加到工程里,否则在大的项目里调试此类问题(尤其是XAML这类既缺乏编译器检查,又没有调用堆栈可查的东西)真够杀死你几万个脑细胞的。

posted @ 2009-11-12 17:38 Shuhari 阅读(1167) 评论(1) 编辑

2009年11月11日

Silverlight陷阱:注意程序集引用问题

假定我要用Silverlight类库实现一些通用控件,然后在应用程序中引用这个控件库。当然,控件通常也要访问其他一些第三方或开源的开发包,例如Silverlight Toolkit。

于是这个项目的依赖关系如下: Silverlight Application => Silverlight Control => Silverlight Toolkit。在Visual Studio中创建好项目之间的引用关系:

 

 

然后在类库项目中创建一个简单的控件,比如:

 

<UserControl x:Class="SLLib.TestControl"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:controlsToolkit
="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit" 
    
>
    
<Grid x:Name="LayoutRoot">
        
<controlsToolkit:DockPanel>
        
</controlsToolkit:DockPanel>
    
</Grid>
</UserControl>

 

最后,在应用程序中添加我们刚刚创建的控件:

<UserControl x:Class="TestSL.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    mc:Ignorable="d"
    xmlns:lib="clr-namespace:SLLib;assembly=SLLib">
    
    
<Grid x:Name="LayoutRoot">
        
<lib:TestControl />
    
</Grid>
    
</UserControl>

 

 这么简单的程序(一行代码也没有),不可能出问题吧?可惜事实上不是这样:

 

 

原因在哪呢?我们打开.xap 文件看看,就会发现问题:Toolkit程序集竟然没有被包含进来!这样控件运行的时候是无法找到DockPanel类的,程序自然就出错了。

 

 

我们可以从其他方面来验证这个错误。删掉原来的控件(其实不删也可以) ,从代码创建一个控件:

public class TestControl2 : ContentControl
{
    
public TestControl2()
    {
        
this.Content = new DockPanel();
    }
}

 然后把程序中的TestControl换成TestControl2,再试试看怎么样?运行正常!.xap文件现在也包含Toolkit了:

 

 

另一方面,如果我们在应用程序的引用中手工加上System.Windows.Controls.Toolkit,那么程序也可以运行正常。

 

这些迹象表明,Silverlight编译器实在有点自作聪明。即使我们在类库引用中明确指定了要引用的程序集,编译器也会忽略这些指示,只查找代码中使用到的那些。对于你在.xaml中引用的程序集,编译器根本不予理会。让情况更加恶化的是,如果运行时找不到类,那么Silverlight运行时只会抛出臭名卓著的AG_E_PARSER_BAD_TYPE,这个毫无内容的错误信息对查找问题没有什么帮助。奇怪的是对于Application类型的项目,Silverlight编译器的做法则完全不同——只要在项目引用中加入了任何程序集,无论实际上是否被用到,都会编译到最终的.xap文件中。这种不一致的行为是你应当小心的。

 

此问题最简单的work around就是:只要在类库中引用了哪些程序集,在应用程序中也保证引用同样的程序集,就可以避免出现错误。显然这不是一个很理想的办法,不仅因为它迫使程序员重复做一些没有实际意义的工作,也使得类库的使用者不得不去关心类库的内部机制,从而让类库的存在意义大打折扣。

posted @ 2009-11-11 11:48 Shuhari 阅读(1322) 评论(4) 编辑

2009年10月29日

WPF/Silverlight陷阱:XAML自定义控件的嵌套内容无法通过名称访问

摘要: 为了说明这个问题,假定我们需要实现一个具有特殊功能的按钮控件。编写Xaml文件如下:[代码]对 Code Behind类,唯一的改动是把向导生成的基类从UserControl改成Button:[代码]然后在主窗体中放上这个新创建的控件:[代码]看起来很平常的代码,但是很遗憾,编译无法通过。Visual Studio会告诉我们这样的信息: 无法在元素“TextBlock”上设...阅读全文

posted @ 2009-10-29 12:18 Shuhari 阅读(1827) 评论(23) 编辑

2009年10月19日

用TDD方式实现老赵的SearchCriteriaBinder

摘要: 看了老赵的 我的TDD实践:可测试性驱动开发(下),我认为这种开发方式并无问题,不过我感到奇怪的是何以老赵会认为用TDD来完成这个工作就会显得尴尬?下面我用TDD的方式来完成同样的工作,读者可以自行比较和老赵的方法有何区别。首先需要说明两点:1. 我不熟悉ASP.Net MVC,自己的机器上也没有装此框架,所以代码仅作为示意,并未编译测试,大家能明白意思就好;2. 我不能说自己的方法一定是正宗的T...阅读全文

posted @ 2009-10-19 15:50 Shuhari 阅读(1824) 评论(30) 编辑

2009年9月30日

自己动手,丰衣足食——补充Silverlight的布局系统

摘要: 我对Silverlight的布局系统一直感到不满,原因无他,太过罗嗦尔。它灵活是非常灵活,但对于界面布局并没有提供一个简洁的表达机制,使得即使简单的界面布局也常常生成一大坨又臭又长的XAML,写得累,看的人更累。在忍受这些毛病很长时间以后,我终于决定抛开Silverlight的默认布局容器,自己写一套更加方便的自定义布局管理器。阅读全文

posted @ 2009-09-30 12:38 Shuhari 阅读(2023) 评论(10) 编辑

2009年9月19日

Silverlight 山寨版样式浏览器

摘要: 不知道是不是Silverlight社区根本没有这个需求,人家Flex Style Explorer已经出来N年了,Silverlight这边还找不到一个对应的工具,难道大家全部手写界面都没有一点怨言?我们在上个项目中使用了Silverlight,但是大家知道Silverlight现在那个设计器根本只能算文本编辑器,没什么可视化功能,我们在调整界面的时候哪怕只修改一个像素,都要重新编译运行程序才能看...阅读全文

posted @ 2009-09-19 16:57 Shuhari 阅读(1617) 评论(4) 编辑

2009年9月16日

使用约定对Silverlight应用进行国际化

摘要: 关于对Silverlight进行国际化的方法,以前已经有过不少文章,我了解到的比较详尽的应当算是TerryLee的这一篇(http://kb.cnblogs.com/page/42913/)。正如这篇文章所指出的,Silverlight对国际化的支持存在不少小瑕疵,不过我最无法接受的问题是语法实在太过冗长。我在项目中为了减少国际化的工作量,同时也为了让代码变得干净清爽一点,采用了另外一种基于约定的国际化方案。阅读全文

posted @ 2009-09-16 17:59 Shuhari 阅读(228) 评论(0) 编辑

<2012年2月>
2930311234
567891011
12131415161718
19202122232425
26272829123
45678910

导航

统计

公告

昵称:Shuhari
园龄:2年8个月
粉丝:1
关注:0

搜索

 
 

常用链接

我的标签

随笔分类

随笔档案

相册

最新评论

阅读排行榜

评论排行榜

推荐排行榜