如果我们有如下的类定义:
public abstract class FruitBase {
}
public class Apple : FruitBase {
}
public class Orange : FruitBase {
}
public class FruitCollection : System.Collections.ObjectModel.Collection<FruitBase> {
}
public class Menu {
public FruitCollection Fruits { get; set; }
}
对于Menu的一个实例,比如olympicMenu,其Fruits属性可能只含有Apple,但是也许全是Orange,或者是多种FruitBase字类实例的集合。
如果我们还定义了Fruit这样一个类:
public class Fruit : FruitBase {
}
这样就很有可能让一些程序员产生错觉了,以为Apple, Orange都是Fruit的子类。而Menu的Fruits这个名字,似乎也提示其是Fruit的集合,而不是FruitBase的集合。
在一个需要对olympicMenu.Fruits中的每个实例进行检查的代码中,如果程序员忘掉了这一点,就可能会写出这样的代码:
for (int i = 0; i < olympicMenu.Fruits.Count; i++) {
Fruit f = olympicMenu.Fruits[i];
//...Op on f
}
幸运的是,编译器会给出这样的错误信息:Cannot implicitly convert type ‘FruitBase’ to ‘Fruit’. An explicit conversion exists (are you missing a cast?)
因为explicit必须显示写出,程序员的错误得以避免。但是,foreach显然是要比for循环更简练。如以下的代码所示:
foreach (Fruit f in olympicMenu.Fruits) {
//...Op on f
}
这个代码却不再会得到编译器的错误提示了!foreach隐含了显式的类型转换!编译通过!如果在调试运行时olympic.Fruits的元素都是Fruit类型,这个错误也不能在调试时发现。这样发布的产品就有了这样一个bug。在运行时,如果olympic.Fruits含有Orange, Apple类的实例时,InvalidCastException将会出现。
FruitBase这个抽象类,就这样又被人记起了。
===
写这个blog,是因为今天分析了一个在已经发布的SP1中的bug。VS 2008 SP1的代码中并没有FruitBase, Fruit, Apple, 和Orange, 但与之相对应的有WPF中的:TriggerBase, Trigger, MultiTrigger, 和DataTrigger.
这行代码,
foreach (Trigger trigger in curStyle.Triggers)
导致了一个需要通过发布补丁的大bug. 成本是很大的。如果Trigger的命名是PropertyTrigger,不但可能避免这个混淆,而且也更加精确地描述了其功用。
回想几年前WPF还在开发的时候,我就提出过这一意见。当然有支持的声音。但是最后还是用Trigger这个名字了。其最有力的理由是什么呢?
且听微软实录下一篇。
一个类型的FullName是能够在一个Assembly(程序集)里唯一限定的名字,所以至少要有名称空间(Namespace)的信息。而一个类型的AssemblyQualifiedName则还要唯一限定了Assembly的名字。
如果Type t = typeof(bool);其Name, FullName和AssemblyQualifiedName分别是
Boolean
System.Boolean
System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
如果Type t = typeof(Nullable<bool>);呢?能不能设想其FullName会是什么样的?
你会注意到其FullName中bool类型显示的是AssemblyQualifiedName。不能只使用bool的FullName,虽然System.Boolean是在其定义的assembly(即mscorlib)中唯一指定了此类型,在别的Assembly中也可能有另外一个类有着相同的FullName。所以,为了能够在一个Assembly中(也是mscorlib)唯一限定Nullable<bool>, bool必须使用AssemblyQualifiedName才正确。
如果Type t = typeof(Nullable<bool>);其Name, FullName和AssemblyQualifiedName分别是:
Nullable`1
System.Nullable`1[[System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
System.Nullable`1[[System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
贴子以"现状"提供且没有任何担保也没有授予任何权利
以下这段代码有编译错误:
public interface IExample
{
void ShowExampe(string example);
}
public class DetailedExample : IExample
{
public void ShowExampe(string example) //Line 7
{
//implementation detail
}
}
public class BlogDetailedExample : DetailedExample
{
public override void ShowExampe(string example) //Line 14
{
//My override
}
}
错误代码是CS0506: 'function1' : cannot override inherited member 'function2' because it is not marked "virtual", "abstract", or "override"。从此我们也许可以推断出第七行的ShowExample不是虚函数。实际上,它是虚函数,至少从IL代码可以看到virtual的标记:
method public hidebysig newslot virtual final instance void ShowExampe(string example) cil managed
但是我们注意到了final的使用,这使得此虚函数不可以再被override. 现在将第七行改成 public virtual void ShowExampe(string example) 则可以使得代码编译通过。此时其IL就和类自己定义的(而不是通过实现interface得到的)虚函数一样了:
.method public hidebysig newslot virtual instance void ShowExampe(string example) cil managed
当然我们也可以将类DetailedExample改成abstract(抽象)类也使得编译成功,这也和类自己定义的其他抽象函数一样。
public abstract class DetailedExample : IExample
{
public abstract void ShowExampe(string example);
}
ShowExample的IL代码如下:
.method public hidebysig newslot abstract virtual instance void ShowExampe(string example) cil managed
所以编译器只是对于未使用virtual或者abstract的interface函数实现做了特别的处理,使得它是虚函数,但是final的函数实现所以不可以再被override. 回答标题的问题,答案是肯定的。
在C#之中,标记一个虚函数为final的关键字是sealed。如第十四行就可以这样写.
public sealed override void ShowExampe(string example)
贴子以"现状"提供且没有任何担保也没有授予任何权利
在C/C++下合法的语句:
int test = 1;
if (test != 0)
{
char test = 'a';
}
却不能在C#下编译,得到CS0136的错误: A local variable named 'test' cannot be declared in this scope because it would give a different meaning to 'test', which is already used in a 'parent or current' scope to denote something else,
在C#规范8.5.1之中对此作了明确的规定:Within the scope of a local variable, it is a compile-time error to declare another local variable or constant with the same name.
所以C#对局部变量的声明更严格。而之所以有这样的规定,我想主要目的是防止编程的无意之中的错误。
贴子以"现状"提供且没有任何担保也没有授予任何权利
在DEV322课程上使用的范型性能演示代码可以在此处得到。这是一个Console Project。
代码还使用了anonymous method的用法。
排行榜在此.
?
这里是周末休闲之.NET Quiz微软技术教育大会Distributed版本。TechEd期间每天发布时间不能固定。正确答案未必能够及时回复。
?
今天我在从广州到上海的转换之中用公共计算机发的Post.
?
在C#2.0中可以使用以下的代码:
... Global::System.IO.File.Exists(@“C:\boot.ini”) ...
?
请说明::的含义和意义所在。
ASP.NET 2.0和Avalon都使用了Partial Class. 这样用户的代码和工具生成的代码可以共同组成一个Class.对Partial Type的支持来自编译器. 4个要点如下:
- 我们把partial 叫做modifier而不是关键字.
- 同一类型在不同文件里面都需要使用partial
- 可以只在一个文件,两个文件,或者更多文件中使用partial
- Base class只需要声明一次;若多次声明必需完全一致.
例子:
Contact1.cs
using System;
namespace Realize.Net.Potential.Sample1
{
public partial class Contact
{
public Contact(string firstName, string lastName)
{
this.firstName = firstName;
this.lastName = lastName;
}
int partial = 0;
private void PartialIsNotAKeyword()
{
partial++;
}
}
public partial class AdvancedContact: Contact
{
public AdvancedContact(string firstName, string lastName, string address) : base(firstName, lastName)
{
this.address = address;
}
}
}
Contact2.cs
using System;
namespace Realize.Net.Potential.Sample1
{
public partial class Contact
{
string firstName;
string lastName;
}
}
Contact3.cs
using System;
namespace Realize.Net.Potential.Sample1
{
public partial class Contact
{
public virtual string Name
{
get { return firstName + " " + lastName; }
}
}
public partial class AdvancedContact
{
string address;
public override string Name
{
get
{
return base.Name + " at " + address;
}
}
}
}
MainFun.cs
using System;
namespace Realize.Net.Potential.Sample1
{
public class MainFunc
{
[STAThread]
static int Main (string[] args)
{
Contact contact = new Contact("Zhanbo", "Sun");
Console.WriteLine(contact.Name);
AdvancedContact advContact = new AdvancedContact("Zhanbo", "Sun", " 1 Microsoft Way ");
Console.WriteLine(advContact.Name);
return 0;
}
}
}
To Compile:
csc /out:MySample1.exe /t:exe *.cs
深入了解Generics, iterators, anonymous methods, and partial classes的最佳读本就是更新的C#2.0Specification.
贴子以"现状"提供且没有任何担保也没有授予任何权利。 (首次发布在11/11/2003)
再一个WinForm中添加对其包含的一个按钮button1的Click事件的event handler,我们在C# V1.x中常常这样写:
this.button1.Click += new System.EventHandler(this.button1_Click);
private void button1_Click(object sender, System.EventArgs e)
{
MessageBox.Show("Button 1 is clicked");
}
当然VS.NET的支持让我们不需要手工写出这么多代码。不过当event handler不需要使用sender, e的情况下,能不能只写出event handler的body而无需考虑代理的signature?
在C# V2.0,anonymous method提供了这一可能。上面的代码可以改写为:
this.button1.Click += delegate
{
MessageBox.Show("Button 1 is clicked");
};
贴子以"现状"提供且没有任何担保也没有授予任何权利。
C# 1.0版本下,可读可写属性的getter和setter必须有相同的visibility(可访问性)。这不能满足有些情况下的需求。2.0就解决了这个问题。如下面例子所示,Capacty的setter比getter的Visibility要小。
public int Capacity
{
get
{
return capacity;
}
internal set
{
capacity = value;
}
}
private int capacity = 0;
智力/知识测试:这样的代码拿到1.0的csc上编译,会得到什么样的输出呢?