C#3.0多了扩展方法和匿名类型,今天探讨下这两个知识点一些容易忽视的地方
项目:C# 控制台应用程序
版本:.net 3.5
运行IDE:VS 2008 SP1
项目文件:Ext.cs、Anonymous.cs、Program.cs
一,扩展方法就近原则
首先要注意扩展方法可以扩展:类、接口、结构体
其次扩展方法有就近原则,也就是如果在你的程序里有两个一模一样的扩展方法,一个和你的使用类是处于同一命名空间里,另外一个处于别的命名空间里,这个时候会优先使用同一命名空间里的扩展方法,也就是说“血缘关系”越近,越被青睐(此句为转摘)。
那么在同一命名空间下在有同名扩展方法时是否会有就近原则呢?答案是No,会报错,请看:
c#类库文件:Ext.cs
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ExtAndVar
{
public class Ext
{
}
static class ExtClassOne
{
public static void ExtOutPut(this Ext ext,string value)
{
Console.WriteLine(value);
}
}
/*static class ExtClassTwo//如果这个类存在,调用ext.ExtOutPut("");会出现编译错误:在以下方法或属性之间的调用不明确:“ExtAndVar.ExtClassOne.ExtOutPut(ExtAndVar.Ext, string)”和“ExtAndVar.ExtClassTwo.ExtOutPut(ExtAndVar.Ext, string)”
{
public static void ExtOutPut(this Ext ext, string value)
{
Console.WriteLine(value);
}
}*/
}
另外有个容易被忽视的知识点:扩展方法实现为静态方法,并使其至少具有与包含类相同的可见性。
如果像下面这么写会报错:可访问性不一致: 参数类型“ExtAndVar.Ext”比方法“ExtAndVar.ExtClassOne.ExtOutPut(ExtAndVar.Ext, string)”的可访问性低
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ExtAndVar
{
class Ext
{
}
public static class ExtClassOne
{
public static void ExtOutPut(this Ext ext, string value)
{
Console.WriteLine(value);
}
}
/*static class ExtClassTwo//如果这个类存在,调用ext.ExtOutPut("");会出现编译错误:在以下方法或属性之间的调用不明确:“ExtAndVar.ExtClassOne.ExtOutPut(ExtAndVar.Ext, string)”和“ExtAndVar.ExtClassTwo.ExtOutPut(ExtAndVar.Ext, string)”
{
public static void ExtOutPut(this Ext ext, string value)
{
Console.WriteLine(value);
}
}*/
}
言下之意就是被扩展类Ext的访问性不能比ExtClassOne低也不能比ExtOutPut低,这么做是为了保证能访问到ExtClassOne.ExtOutPut的地方也能访问到Ext
二、匿名类型成员允许的类型
匿名类型的成员都是公共只读属性,所以只要属性支持的成员就可以作为匿名类型的成员
以下是MSDN的原话:匿名类型是由一个或多个公共只读属性组成的
下面这个例子我总结了下匿名类型支持的成员,以及隐式类型var的一些问题:
c#类库文件:Anonymous.cs
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ExtAndVar
{
class Anonymous
{
public delegate void TestHandler();
public static event TestHandler et;
public static event TestHandler ett
{
add
{
}
remove
{
}
}
public static void Handler()
{
Console.WriteLine("Call Handler!");
}
public static void TestOne()
{
var t = new StringBuilder();
t.Append("看到没?var引用完全可以调用被引用对象的成员");
object obj = new StringBuilder();
//obj是object的引用,只能调用object表现出来的成员
Console.WriteLine(t is object);//输出True,因为t引用StringBuilder,StringBuilder继承object
}
public static void TestTwo()
{
var t = new {i=3};
//t.i = 4;这句会编译错误,记住匿名类型内的成员都是公共只读属性!
var r = new {s=new StringBuilder() };//匿名类型的成员可以是任何C#属性支持的成员
r.s.Append("");
}
public static void TestThree()
{
TestHandler th = Handler;
var t = new {th};//这表明匿名类型成员可以是委托实例
t.th();
}
public static void TestFour()
{
et += Handler;
var t = new { et };//这表明匿名类型成员可以是隐式声明的事件(ps:因为隐式声明的事件就是一个委托实例)
t.et();
ett += Handler;
//var r = new { ett };//编译出错,匿名类型的成员不能是显式声明的事件(ps:因为显式声明的事件是一种特殊的C#属性,匿名类型成员不能是属性)
}
public static void TestFive()
{
var al = new[] { new { a = 1 }, new { a = 2 }, new { a = 3 } };//请注意匿名类型数组在初始化的时候不能指定数组长度,例如new[3] { new { a = 1 }, new { a = 2 }, new { a = 3 } }编译会出错,因为指定了长度3
//var bl = new[] { new { a = 1 }, new { a = 2 }, new { b = 3 } };//编译出错,说明匿名类型数组里面装的如果也是匿名类型,要求所装的匿名类型是同一个类型(即匿名类型结构相同)
int count = al.Length;
}
public static void TestSix()
{
var t = new {i=3 };
//var r = new {int i=3 };//编译出错,匿名类型成员不需要指定类型,匿名类型成员的类型会从赋值中推断出来
}
public static object TestSeven()
{
var t = 1;
return t;//可以用object返回var类型
}
public static int TestEight()
{
var t = 1;
return t;//也可以返回var实际引用的类型
}
}
}
最后是一个CLR启动文件,用于调用上面两个测试:
Program.cs:
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ExtAndVar
{
class Program
{
static void ExtInvoke()
{
Console.WriteLine("------Ext------");
Ext ext = new Ext();
ext.ExtOutPut("Test");
Console.WriteLine("\n");
}
static void AnonymousInvoke()
{
Console.WriteLine("------Anonymous------");
Anonymous.TestOne();
Anonymous.TestThree();
Anonymous.TestFour();
Console.WriteLine(Anonymous.TestSeven());
Console.WriteLine(Anonymous.TestEight());
}
static void Main(string[] args)
{
ExtInvoke();
AnonymousInvoke();
}
}
}