VS2010被認爲將是續寫Visual Studio 6 的扛鼎之作。整個IDE不僅是使用了WPF重構,而且使用了最新的NET Framework 4作爲強大的後援支撐。從上至下可圈可點。下面我們就來看一看VS2010在哪些方面引人注目——
1)WPF重構界面:整個VS2010 IDE全部使用WPF重構,因此與Win7外觀緊密集成,而且實現了先前所有NET版本所不能實現的一些功能——比如代碼的無極縮放(打開一個項目應該可以看到左下角的顯示比率,默認100%;這樣您不必切換字體大小了,可以手動輸入百分比,可以下拉選擇,當然更可以直接Ctrl+鼠標滾輪快捷方式進行調整)。
2)快速搜索:
I)如果想尋找某個類(方法等)在何處調用,直接選中這個方法(類名),IDE會自動在當前打開的文檔中使用淡紫色圈出所有的這個類(方法)名稱。
II)快捷鍵“Ctrl+逗號”呼出搜索類和方法框,直接輸入類名(不區分大小寫,可以使用Pascal輸入形式)自動列出所有的類和方法、屬性等。
3)架構體系查看:
要想知道某個項目究竟有哪些文件,它們之間的調用關係等,在VS2010易如反掌——您所要做的只是打開架構瀏覽器(位於View菜單下的Architecture Explorer),然後就可以通過點擊Solution View或者Class View查看當前項目(或者整個解決方案)的類、方法等。還可以通過文本框輸入進行檢索(點擊一個漏斗圖標,檢索方式同“快速檢索”)。您更可以使用Ctrl+A的方式選中全部的類(方法),點擊“Architecture Explorer”左邊第一個按鈕,自動創建生成關聯圖。
當然,你想要知道某個方法在哪些地方被調用了,可以在該方法上右鍵,選擇“Call Hierarchy”(顯示層次關係)即可。
4)第三方程序的測試:
您可以在完全不知道第三方的程序情況下對其測試,這個是一個重大的突破。首先您創建一個Test Project,右鍵加入Coded UI Test文件,打開後選中“Record actions(錄製行爲)”那個選項,然後打開一個第三方的程序(比如畫圖板等),你隨隨便便做一些操作(在此之前務必按下右下角的錄製動作的按鈕),然後等到完畢之後再次點擊那個停止記錄的按鈕,然後點擊右邊那個“Generate Codes”(生成代碼)就可以生成代碼,您可以對這些代碼進行調試了。
5)可選參數和命名話參數(C#):
早些時候如果你想省略某些函數的參數,您不得不定義多次重載該函數以便獲得這些函數的不同參數形式。在VB.NET中自帶參數省略的功能,但是C#的程序員只能望塵莫及。現在不必了!C#也完全可以這麼做,爲您少些諸多重載函數打開方便之門。比如:DoTask (string taskName, bool Repeat=false) {……},但是可缺省參數必須在最後定義,例子中把Repeat移到taskName前是絕對不允許的,而且缺省參數的賦值必須是const類型(要不是寫死的,要麼是const變量,不能是其它的)。
與此同時,VS2010中還支持亂序給參數賦值——什麼意思?如果某個函數有多個參數,你只要(函數名:數值)這種方式,您就可以隨心所欲給任何函數參數賦值了。
假如有一個接口
6)協變和反變(Co-variant & Crop-variant)
這是VS2010新增的一個內容,用於在編譯的時候確認是否允許不同類型的泛型接口之間是否存在轉換的問題。
爲了瞭解“協變”和“反變”的概念,我們先看一個例子:
假設我們定義了一個接口和若干類:
class Father
{
public virtual void Say()
{
Console.WriteLine("Father");
}
}
class Son : Father
{
public override void Say()
{
Console.WriteLine("Son");
}
}
class Son2 : Father
{
public override void Say()
{
Console.WriteLine("Son2");
}
}
Interface InAndOut<T, V>
{
void Input(V value);
T Output();
}
class Program<T,V>:InAndOut<T,V>
{
private object value = default(V);
public T Output()
{
return (T)value;
}
public void Input(V tv)
{
value = tv;
}
}
又假如我們已經實例化兩個接口的實例:
InAndOut<Father, Son> iobj1 = new Program < Father, Son >();
InAndOut<Son, Father> iobj2 = new Program < Son, Father >();
現在我們令:iobj1= iobj2,可行嗎?
乍一看似乎可行——爲什麼呢?因爲聯想到右邊的兩個子類Son會被自動隱式轉化成其父類Father。就好像是Father f = new Son()一樣可以(注意:我們把子類隱式轉化成父類成爲“協變”,反之成爲“反變”)。而且,根據接口定義,輸入方向是接受一個Son(實際存儲在iobj2中,被隱式轉成Father),輸出的時候還是存儲的Son被隱式轉化成Father輸出。
這種思考邏輯固然沒有錯,但是它有一個前提條件——即從iobj1輸入方向看,必須是Son到Father,輸出的話也必須是Son到Father!但是泛型僅僅是一個定義,誰能夠保證在類中Father或者Son一定是輸入(或者是輸出)參數呢?如果我們改變成以下的形式呢?
class Program<T,V>:InAndOut<T,V>
{
private object value = default(T);
public V Output()
{
return (V) value;
}
public void Input(T tv)
{
value = tv;
}
}
這樣就出現了問題——首先,iobj1指向iobj2(接受一個Father的參數,此時如果我的Father輸入的是Son2,那麼實際轉化到Son的時候就發生異常了;同樣地,輸出因爲是Son2不能轉化成Son,因此發生異常)。
這個問題就是因爲輸入輸出方向不明確所導致的。如果我們強制是一開始就給出的輸入(輸出方向)。即V只能作爲輸入,T只能作爲輸出就可以了。
推而廣之,假設存在兩個泛型T和V,假設V:T(V是T的子類)。那麼泛型接口之間轉換的一般規則是:輸出類型是父類,“輸出”一般是協變;反之,輸入類型是子類,一般是反變。約束這種輸入輸出泛型的規則就是:輸出線的接口加關鍵詞out,輸入加in。如下所示:
Interface InAndOut<out T, in V>
{
void Input(V value);
T Output();
}
那麼你輸入以下代碼,就可以輸出結果:
InAndOut<Father, Son> iobj1 = new Program < Father, Son >();
InAndOut<Son, Father> iobj2 = new Program < Son, Father >();
這種規則本質上是編譯器的行爲理解。但是不一定就是正確結果,考察下列例子:
InAndOut<Father, Son> iobj1 = new Program < Father, Son >();
InAndOut<Son2, Father> iobj2 = new Program < Son2, Father >();
這一段代碼照樣可以通過編譯,但是運行仍舊報異常。爲什麼呢?因爲“輸入端”和“輸出端”儘管都符合了隱式轉換的條件,但是你注意:把一個Son對象存儲到iobj2的時候,iboj2的輸出要求是Son2,而不是Son! 因此要保證運行正確,必須做到這樣:
InAndOut<Father, Son> iobj1 = new Program < Father, Son >();
InAndOut<Son, Father> iobj2 = new Program < Son, Father >();
輸入端接受Son,能夠隱式轉成藍色的Father,藍色Father存儲的子類對象同樣必須可以轉化成Son(即一個協變的東西必須能夠支持其反變;反之,一個反變的泛型必須支持其協變泛型,這就是著名的“協變反變類型”,簡稱“協-反變類型”)。
證明:(紅色的Son開始):隱式轉化成Father(協變),然後藍色的Father(其中存儲Son)強制轉化成綠色的Son(反變)。
同理,(黑色的Father開始):黑色Father返回的內容(存儲Son)可以強制轉化成藍色Father的內容(反變),同時可以隱式轉化成綠色Son(協變)。我覺得可以使用對角線規則驗證(猜想,對於任意的泛型A,B,C,D):
InAndOut<A,B>
(B既可以轉成D,也可以轉成C;輸出A包含的內容
可以轉化成D,也可以轉化成C)
InAndOut<C, D>
VS2010之所以那麼強大,究其原因是其背後有着強大的C#4.0作爲後臺支撐。和以往的所有版本相比,C#4.0的動態性大大增強——dynamic就是一個非常明顯的例子:
(一)dynamic初探:
以前因爲某些特殊原因,需要動態的調用外部類(假設這個類是實現了某個帶有參數的接口函數的),通常我們只能用反射了。示例代碼如下:
Assembly asm = Assembly.LoadFile(“xxxxx”)
asm.CreateInstance("MyAssembly.ClassName").GetType().InvokeMember("Say", BindingFlags.InvokeMethod, null, asm.CreateInstance("MyAssembly.ClassName "), new string[] { "aaa" });
這裏順便簡略說一下反射流程:首先通過絕對路徑加載某個NET的dll文件,然後創建該assembly中某個class的instance(該class必須有無參構造函數),獲取其類型之後動態調用其函數Say,“BindingFlags.InvokeMethod”表明是一個普通類方法,“null”的地方是傳遞一個參數名的,和指明最後的string[]中的一串values內容一一匹配的……可見使用反射調用函數是很痛苦的一件事情。
現在呢?您根本不需要那麼麻煩了!因爲C#的dynamic會爲您做好一切的,下面就是見證奇蹟的時刻——
Assembly asm = Assembly.LoadFile("xxxxx");
dynamic dfun = asm.CreateInstance("MyAssembly.ClassName");
dfun.Say("Hello!");
注意到咖啡色的代碼了麼——什麼?dynamic竟然可以智能感知出動態加載的那個類的方法Say?其實不然:當你按下這個點的時候,IDE是沒有智能感知的,但是如果你知道這個類是有這個方法(因爲接口給了其一個契約,必須實現接口中的方法;而接口的方法是公開的),你就可以完全不理會智能感知,照樣寫,照樣編譯通過運行。神奇吧!
看到這裏,你就不會認爲dynamic和var是“差不多”的概念了(var無非是根據賦值的類型編譯器自己判斷;且var不能作爲函數返回值類型,但是dynamic可以)。
或許有人會疑問:dynamic可以完全替代類似像簡單工廠、抽象工廠一類的東西了咯?我的理解是——不對!從上面的定義中可以得知:dynamic必須首先獲取對象實例,然後動態反射是它做的事情;如果完全取代反射,實例也獲取不到,如何反射呢?真是“巧婦難爲無米之炊”啊!
說道dynamic可以作爲返回值,下面給出一個例子:
class DynamicClass
{
public int Num1 { get; set; }
public int Num2 { get; set; }
public DynamicClass(int n1, int n2)
{
Num1 = n1;
Num2 = n2;
}
public dynamic DynamicAction
{ get; set; }
}
主函數注意咖啡色部分:
static void Main(string[] args)
{
DynamicClass t = new DynamicClass(1, 2);
t.DynamicAction = new Func<int, int, double>((x, y) => x + y);
Console.WriteLine(t.DynamicAction.Invoke(t.Num1,t.Num2));
}
道理很簡單:因爲dynamic類型可以賦值任何東西(包括匿名委託),所以我創建了一個匿名委託給它。然後調用計算結果(匿名委託的調用使用Invoke,可以省略)。
但是……dynamic不僅僅可以動態反射類方法和屬性,還可以“空中樓閣”般動態地去創建一個類方法和屬性,並且賦值,相信嗎?這是第二話。
(二)神奇的ExpandoObject類和自定義動態類擴展:
dynamic在第一話中已經展示它動態根據賦值類型直接自動完成反射的強大功能。現在又是一個新奇蹟的誕生——
static void Main(string[] args)
{
dynamic d = new ExpandoObject();
d.Name = "ServiceBoy";
d.Action = Func<string>(()=>d.Name;);
Console.WriteLine(d.Action());
}
初看這個代碼只是簡單的讀寫Name屬性,毫無稀奇可言。但是你注意哦——你到MSDN——或者你索性new ExpandoObject().Name 試試看,有Name和Action這個屬性嗎?——沒有啊,真的沒有!嘿,奇了怪了,既然沒有,爲什麼你可以憑空“捏造出一個屬性”,而且可以給屬性賦值,並且讀取屬性內容呢?
俗話說的好——天下沒有白給的食——微軟這個類意在向我們揭露一個驚天的大祕密,那就是你可以自定義dynamic類,讓這個類跟隨你的要求動態的改變自己(比如增加一個新屬性等)。我們可以參照MSDN,給出一個自定義的ExpandoObject:
public class SimpleDynamic : DynamicObject
{
Dictionary<string, object> Properties = new Dictionary<string, object>();
Dictionary<string, object[]> Methods = new Dictionary<string, object[]>();
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
if (!Methods.Keys.Contains(binder.Name))
{
Methods.Add(binder.Name, null);
}
if (args != null)
{
Methods[binder.Name] = args;
}
StringBuilder sbu = new StringBuilder();
foreach (var item in args)
{
sbu.Append(item);
}
result = sbu.ToString();
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (!Properties.Keys.Contains(binder.Name))
{
Properties.Add(binder.Name, value.ToString());
}
return true;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return Properties.TryGetValue(binder.Name, out result);
}
}
首先說明這個例子的作用:隨意增加不重複的屬性並賦值(取值),並且讓你隨意創建或者調用(帶參或無參)函數進行輸入(輸出)。
分析一下這個類的主要特點:
一般地,任何一個類——如果需要動態爲自身添加屬性、方法等的,就必須實現IDynamicObjectProvidor接口或者是DynamicObject虛類(之所以用虛類的原因是“各取所需”的緣故,DynamicObject類都通過虛方法virtual去“實現”了接口中所有的方法,只要繼承了這個類,讀者可以根據需要“任意”動態覆蓋你要的方法)。這裏介紹三個最常見的方法:
- 如果需要支持動態創建寫屬性,必須覆蓋TrySetMember,其方法介紹如下:
參數名稱 |
作用說明 |
binder:SetMemberBinder類型 |
用於獲取動態創建賦值屬性的時候“屬性名”等一些常見信息(如示例中Name獲取動態賦值的那個屬性)。 |
value:object類型 |
用於獲取設置動態屬性的那個值。 |
- 如果需要支持動態創建讀屬性,必須覆蓋TryGetMember,其參數作用和TrySetMember大致相當,只是反作用(用於獲取某個已有屬性的內容,並且反向傳遞給object作爲輸出結果,注意TryGetMember的value是一個out類型)。同時,這個函數多出一個result類型,用於返回已有屬性的存儲的值(NULL拋出異常,被認爲是錯誤的)。
- 如果需要動態調用函數並輸出結果,必須覆蓋TryInvokeMember方法,此函數比較複雜:
參數名稱 |
作用說明 |
binder:InvokeMemberBinder類型 |
用於獲取動態創建函數時候一些與函數相關的屬性: (比如Name是函數名,其中還有一個CallInfo內嵌屬性,您還可以獲得ArgumentNames(C#4.0中最新的可選參數的名稱,通過其ArgumentNameCount獲取可選參數名的總個數))。
|
Args:object[]類型 |
獲取動態給函數賦的值。 |
result:object類型 |
返回動態函數執行的結果,Null表示異常。 |
根據以上表格,對照不難讀懂我的示例代碼——現在假設你是這樣調用的:
1)
dynamic d = new SimpleDynamic();
d.Name = “Serviceboy”;
Console.WriteLine(d.Name);
首先創建了一個d的動態類型,然後當賦值給Name的時候,因爲Name是屬性,所以觸發了“TrySetMember”函數,該函數自動檢查是否已經存在這個屬性名,如果不存在,則將其添加進入一個Dictionary中並將對應賦予的值傳遞進去保存起來。當使用輸出的時候,同樣地,TryGetMember被觸發,系統檢測是否預先創建過這個值,如果沒有,則拋出異常;存在的話,取出對應的存儲value並返回給系統。
2)
dynamic d = new SimpleDynamic();
Console.WriteLine(d.Say(“Hello!”));
首先創建了一個d的動態類型,當動態創建一個方法的時候,系統檢測是否包含這個方法名,不包含將添加這個方法名到Dictionary保存,接着檢查參數是否爲空,不爲空把參數賦值給那個函數名作爲Key的Dictionary中保存,最後使用StringBuilder串起來賦值給result作爲輸出。
下面給出一個比較複雜的例子——自定義的XML創建器(仿Jeffery Zhao):
public class XmlCreator : DynamicObject
{
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
//如果除了第一個節點是字符串,後面都是XElement對象,表明此節點是父節點
if (args[1] is XElement)
{
XElement root = new XElement(args[0].ToString());
//把子節點添加到父節點
for (int i = 1; i < args.Length; ++i)
{
root.Add(args[i]);
}
result = root;
}
//否則是子節點
else
{
//拷貝所有屬性到數組:
string[] attributes = new string[binder.CallInfo.ArgumentNames.Count];
for (int i = 0; i < binder.CallInfo.ArgumentNames.Count; i++)
{
attributes[i] = binder.CallInfo.ArgumentNames[i];
}
//拷貝所有屬性值到數組:
string[] values = new string[args.Length - 1];
for (int i = 1; i < args.Length; ++i)
{
values[i - 1] = args[i].ToString();
}
XElement subelement = new XElement(args[0].ToString());
//屬性名稱獲取
for (int i = 0; i < attributes.Length; ++i)
{
subelement.SetAttributeValue(attributes[i], values[i]);
}
result = subelement;
}
return result != null;
}
}
該函數功能是:輸出任意同時帶有屬性的節點,同時可以嵌套——比如:
dynamic xmlCreator = new XmlCreator();
XElement ele = xmlCreator.CreateElement(“Books”,
xmlCreator(“Book”,name:C#,price:100.50)
);
大家可以自己想一想是怎麼一個原理哦。