顯示具有 c# 標籤的文章。 顯示所有文章
顯示具有 c# 標籤的文章。 顯示所有文章

2008-10-04

解決 ADO.NET Data Services 搭配 LINQ to SQL Classes 的問題

在 MSDN 或是網上看到的 ADO.NET Data Services 範例通常都是搭配 ADO.NET Entity Data Model 來展示, 比較少看到使用 LINQ to SQL Classes 的案例, 後來自己試了一下, 才發現原來是因為使用 LINQ to SQL Classes 會有一些小問題, 就是 Table 的 Key 必需要符合 {TableName}ID 的命名原則, 否則就會產生如下圖的伺服器例外, 但如果是使用 ADO.NET Entity Data Model 的話就不會有這樣的問題!

image

 

要解決這個問題是有方法的, 一是打開 .dbml 檔將類別的屬性名稱改成 TableNameID 的格式, 但是這樣好像有點笨, 僅適合用在剛開始開發的階段, 否則光測試就要人命了, 另一種解決方案比較優, 是利用 System.Data.Services.Common.DataServiceKeyAttribute 這個特徵項來標記每個 Table 相關的 partial lass, 如以下的範例:

[DataServiceKey("CustomerID")]
public partial class Customers { }

建議有類似問題的人可以自己新建一個 .cs 檔, 把它和 .dbml 放在一起, 並用類似的命名讓它們在 Solution Browser 裡可以很容易的就被看到, 像是 Northwind.DataServiceKey.cs 之類的, 然後在裡面統一管理所有的 Table classes, 替每個 Table class 加上 DataServiceKey 的特徵項, 這樣子未來才比較好管理! 如果有其他定義 partial method 的需要, 最好再另外建立一個 partial class 檔案來管理, 才不會影響到原來單純的 DataService 定義! 嗯~從這個例子剛好可以實際體會 partial class 在管理上所帶來的好處, 當然前提是命名方式和檔案存放的位罝要先自己規劃好才能享受到它所帶來的好處, 否則打散的類別反而更難了解.

附帶一提, 所有的 Table 都必需要有 key, 如果沒有的話 ADO.NET Data Services 就無法正常運作, 一般來說有經驗的 DBA 是比較不會建立一張沒有 key 的資料表的吧!

其實這個機械式的動作應該由 Visual Studio 幫開發人員處理掉才對, 而不是把這種冗長又容易出錯的工作留給開發人員去處理, 我想 Microsoft 應該也知道這點, 只是可能因為時程或是其他因素而沒有處理到吧!

反正實作上也不會太複雜, 如果有需要的話乾脆就自己寫一小段程式去產生相關的程式碼就可以了, 一時興起寫了兩個版本, 一個使用一般的寫法, 另一個使用 LINQ 的寫法, 很明顯的 LINQ 的語法自然又簡潔多了, 如下:

一般版:

private static void GenerateDataServiceKeyAttributePartialClasses(Assembly asm, StringBuilder sb)
{
    Type[] types = asm.GetTypes();
    foreach (Type t in types)
    {
        object[] attrs = t.GetCustomAttributes(typeof(TableAttribute), true);
        if (attrs.Length == 0) continue;

        List<string> keys = new List<string>();

        PropertyInfo[] pis = t.GetProperties();
        foreach (PropertyInfo pi in pis)
        {
            object[] propAttrs = pi.GetCustomAttributes(typeof(ColumnAttribute), true);
            if (propAttrs.Length == 0) continue;

            if (((ColumnAttribute)propAttrs[0]).IsPrimaryKey)
            {
                keys.Add(pi.Name);
            }
        }
        sb.AppendFormat("\r\n[DataServiceKey(\"{0}\")]\r\n", String.Join(@""", """, keys.ToArray()));
        sb.AppendFormat("public partial class {0} {{ }}\r\n", t.Name);
    }
}

LINQ 版:

private static void GenerateDataServiceKeyAttributePartialClass(Assembly assembly, TextWriter writer)
{
    var tableInfoList = from t in assembly.GetTypes()
                        where t.GetCustomAttributes(typeof(TableAttribute), true).Length == 1
                        select new
                        {
                            TypeName = t.Name,
                            KeyFields = (from p in t.GetProperties()
                                         let columns = p.GetCustomAttributes(typeof(ColumnAttribute), true)
                                         where columns.Length == 1 && ((ColumnAttribute)columns[0]).IsPrimaryKey
                                         select p.Name).ToList()
                        };

    writer.Write("using System.Data.Services.Common;\r\n\r\n");
    foreach (var info in tableInfoList)
    {
        writer.Write("\r\n[DataServiceKey(\"{0}\")]\r\n", String.Join(@""", """, info.KeyFields.ToArray()));
        writer.Write("public partial class {0} {{ }}\r\n", info.TypeName);
    }
}

 

keywords: ado.net, data services

2008-09-27

巢狀 using 排版

原來的格式

using (Rijndael aes = RijndaelManaged.Create())
{
    using (ICryptoTransform cryptor = aes.CreateEncryptor(rgbKey, rgbIV))
    {
        using (CryptoStream cs = new CryptoStream(output, cryptor, CryptoStreamMode.Write))
        {
            byte[] buf = new byte[8192];
            for (int len = 0; (len = input.Read(buf, 0, buf.Length)) > 0; )
            {
                cs.Write(buf, 0, len);
            }
        }
    }
}

調整後的格式

using (Rijndael aes = RijndaelManaged.Create())
using (ICryptoTransform cryptor = aes.CreateEncryptor(rgbKey, rgbIV))
using (CryptoStream cs = new CryptoStream(output, cryptor, CryptoStreamMode.Write))
{
    byte[] buf = new byte[8192];
    for (int len = 0; (len = input.Read(buf, 0, buf.Length)) > 0; )
    {
        cs.Write(buf, 0, len);
    }
}

調整後的程式是不是比較美呢!

 

keywords: c#, coding style

2008-08-31

.NET 1.1 WebRequest KeepAlive 和 WebProxy.Credentials 的奇怪現象

發生在一個 .NET 1.1 的 App 上的問題, 簡化後的程式碼如下:

HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.KeepAlive = false;

request.Proxy = new WebProxy(proxyServer);
request.Proxy.Credentials = new NetworkCredential(userName, password);

HttpWebResponse response = request.GetResponse() as HttpWebResponse;
response.Close();

因為某種原因, 在一開始時刻意將 KeepAlive 屬性設為 false, 導致了這個奇怪現象的發生, 而且只會在 .NET 1.1 底下發生! 症狀是如果 KeepAlive 設為 false 的話, 就會丟出 407 Proxy Authentication Required 的例外, 可是同樣的程式在自家的環境內卻又測不出問題, 所以初步研判應該是 Proxy server 不同的原因所造成, 如果是在 Microsoft ISA Server 的環境中就會發生, 而在 Squid Proxy Server 則沒有這個問題, 這是什麼道理咧?!

道理說穿了很簡單, 因為 Squid Proxy Server 是採用 Basic 的驗證方式, 而 ISA Server 預設卻是 challenge-response 的驗證機制, Basic 的驗證方式是將帳號密碼以 base64 的方式編碼傳送出去, 所以和 server 不需要保持連線, 但是 challenge-response 則必需要保持連線才可以進行驗證, 這就是為什麼當 HttpWebRequest 的 KeepAlive 設成 false 的時候, 在 ISA Server 的環境一直驗證失敗的原因! 而 .NET 2.0 之所以不會發生這個問題是因為有針對這個情況做特別處理, 也就是在進行 challenge-response 驗證時, 會自動將 Proxy-Connection 的 Header 設為 Keep-Alive, 待驗證過後才再設為 Close, 所以不會出問題!

相關知識可以參考 Authentication in WinHttp, Challenge-response authentication 和一篇寫的很詳細的 blog 文章 WebProxy.Credentials does not work when KeepAlive = false in .NET 1.1

keywords: httpwebrequest, proxy, credentials, keepalive

2008-08-09

使用 ADO.NET 接回 SQL Server 的 PRINT Message

常看到有人被這個問題困擾, 自己以前也曾經被這個問題所苦, 可是其實這個問題是很容易解的, 看範例:

using (SqlConnection conn = new SqlConnection("Data Source=(local);Initial Catalog=AdventureWorks;Integrated Security=SSPI"))
{
    conn.InfoMessage += new SqlInfoMessageEventHandler(SqlConnection_InfoMessage);

    conn.Open();
    SqlCommand cmd = conn.CreateCommand();
    cmd.CommandText = "DBCC CHECKIDENT ('HumanResources.Employee', NORESEED)";
    cmd.ExecuteNonQuery();
}

重點就是 SqlConnection.InfoMessage 事件, 可以在事件處理函式中以 e.Message 取得 SQL Server 所輸出的訊息!

順便介紹一個 DBCC 指令 "DBCC CHECKIDENT", 這個指令是用來檢視指定 Table 內有使用自動遞增欄位目前的值, 有需要知道或是想要重設序號為某一個新值的人可以參考該指令的用法!

2008-04-09

Json.NET on codeplex

在 Codeplex 上看到的一個簡單明瞭的 Json 類別庫!

Product product = new Product();
product.Name = "Apple";
product.Expiry = new DateTime(2008, 12, 28);
product.Price = 3.99M;
product.Sizes = new string[] { "Small", "Medium", "Large" };

string json = JavaScriptConvert.SerializeObject(product);
//{
//  "Name": "Apple",
//  "Expiry": new Date(1230422400000),
//  "Price": 3.99,
//  "Sizes": [
//    "Small",
//    "Medium",
//    "Large"
//  ]
//}

Product deserializedProduct = JavaScriptConvert.DeserializeObject<Product>(json);

HttpWebRequest 與 https

HttpWebRequest 透過 SSL 取得網頁資訊時, 如果對方網站的憑證不被自己的電腦所信任的話, 就會出現 "基礎連接已關閉: 無法為 SSL/TLS 安全通道建立信任關係。" 的錯誤, 英文版的錯誤訊息是 "The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.", 在 .NET 1.1 時代曾經用 ServicePointManager.CertificatePolicy 解決過這個問題, 但到了 .NET 2.0 之後, 該方法被列為 obsolete 了, 所以就研究了一下新的方法, 順便做個記錄

// .NET 1.1
ServicePointManager.CertificatePolicy = new AcceptAllCertificatePolicy();

如果是 .NET 1.1, 我們必須自行建立一個像上面範例碼的 AcceptAllCertificatePolicy 類別, 並讓該類實作 ICertificatePolicy 介面, 該介面只有一個方法叫 CheckValidationResult, 回傳 bool 來決定是否接受對方的憑證, 直接 return true 就行了.

public class AcceptAllCertificatePolicy : ICertificatePolicy
{
    public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem)
    {
        return true;
    }
}
.NET 2.0 簡單多了, 只要一行, 透過匿名函式就行了!
// .NET 2.0
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

2008-02-15

WCF 錯誤: 此集合已包含具有配置 http 的位址。此集合中,每一配置最多只能有一個位址。

WCF Service 佈署到 IIS Application Server 之後, 剛開始運作的很順利, 但是後來卻發生以下的問題:

WCF 錯誤: 此集合已包含具有配置 http 的位址。此集合中,每一配置最多只能有一個位址。

剛開始以為是 WCF 有問題, 可是後來重新啟動 IIS 也沒有解決, 所以初步認定應該是設定上的問題, 趕快找 Google 問看看, 輸入錯誤訊息, 搜尋...果然, 找不到想要的, 因為我輸入的是中文...唉...每次都這樣, 只好先嘗試猜測把關鍵字翻譯成英文 collection contains http address, 再搜尋一次, 這一次 Google 終於聽懂我的問題了, 馬上告訴我問題的原因和解決的方法!!

原來是因為 IIS 的站台設定了多重識別才造成這樣的問題, 解決的方法很簡單, 就是在多重識別的設定頁留下一個就好, 其他的移除掉, 問題就可以解決了!!

 

謎之音: 那如果多重識別一定得有多個怎麼辦? 難不成就不能享用 WCF 了嗎?

 

...呃! 我也很想知道, 畢竟只能留一個這樣子的解決方式有點遜, 不算是一個好的解決方案! 那該怎麼做呢, 很簡單, 只要兩個步驟:

  1. 建立一個類別繼承 ServiceHostFactory, 並覆寫 CreateServiceHost 方法, 如下
    public class CustomServiceFactory: ServiceHostFactory
    {
        protected override ServiceHost CreateServiceHost(TypeserviceType, Uri[] baseAddresses)
        {
            return new ServiceHost(serviceType, baseAddresses[0]);
        }
    }
  2. 在原來的 .svc 檔頭部分多加上一個 Factory 屬性的設定, 雖然 intellisense 不會出現, 但是請相信專業, 這個屬性是存在的, 不相信的話請看 Reflector!
    <%@ServiceHost Language="C#" Debug="true" Service="MyCustomService" Factory="CustomServiceFactory"%>

只要這兩步就可以輕鬆解決這個討厭的問題啦!!

2007-12-27

深入 Microsoft.VisualBasic.Strings.StrConv 簡繁轉換

昨天又遇到一個簡繁轉換的需求, 雖然這個問題以前已經處理過了, 但是以前是用自己建立的 b52gb 和 gb2b5 的對應表來完成這個需求(VB6 的話就用 StrConv 方法來達成), 在 .NET 環境中, Microsoft.VisualBasic.dll 裡也有提供 Strings.StrConv 方法, 而且用法和原來的 VB6 幾乎是如出一轍, 可是昨天在使用 StrConv 的時候卻意外發現了一些奇怪的現象, 特別深入研究了一下, 順便記錄下來!

先來觀察 Strings.StrConv 方法的簽名:

public static string StrConv(string str, VbStrConv Conversion, [Optional, DefaultParameterValue(0)] int LocaleID)

第三個參數和 MSDN 上的文件有點不同, 上面的簽名是從 Reflector 中摘出來的, 也是這篇文章要記錄的重點, 先來看一些範例:

    a1 = Strings.StrConv("书樂う반", VbStrConv.TraditionalChinese, 0x0404);    // a1 = "?樂??"
    a2 = Strings.StrConv("书樂う반", VbStrConv.SimplifiedChinese, 0x0404);     // a2 = "????"

    b1 = Strings.StrConv("书樂う반", VbStrConv.TraditionalChinese, 0x0804);    // b1 = "書樂う?"
    b2 = Strings.StrConv("书樂う반", VbStrConv.SimplifiedChinese, 0x0804);     // b2 = "书乐う?"

    c1 = Strings.StrConv("书樂う반", VbStrConv.TraditionalChinese, 0x0412);    // c1 = "?樂う반"
    c2 = Strings.StrConv("书樂う반", VbStrConv.SimplifiedChinese, 0x0412);     // c2 = "??う반"

    d1 = Strings.StrConv("书樂う반", VbStrConv.TraditionalChinese, 0x0009);    // d1 = "書樂う반"
    d2 = Strings.StrConv("书樂う반", VbStrConv.SimplifiedChinese, 0x0009);     // d2 = "书乐う반"

上面 8 個範例的第一個參數摻雜了簡中、繁中、日文和韓文, 第二個參數區分了轉簡體和轉繁體, 第三個參數是 localeID 的部分, 分別包含了 zh-TW (0x0404), zh-CN (0x0840), ko-KR (0x0412), en (0x0009), 讓我們來仔細觀察一下結果, 一切的玄機都在第三個 localeID 參數身上. 我們先假設第三個參數 localeID 是用來表示來源字串的字集, 所以如果這個假設成立的話..., 來看看結果:

  1. a1: 嗯, 一切如預期的結果, 第一步應該先將 "书樂う반" 轉成符合 zh-TW (0x0404) 的字集, 所以結果是 "?樂??", 然後再根據第二個參數 VbStrConv.TraditionalChinese 結果變成了 "?樂??", 正確!
  2. a2: 第一步同上, 然後再根據第二個參數 VbStrConv.SimplifiedChinese 結果應該要變成 "?乐??", 可是 a2 的結果卻得到了 "????", 不如預期!
  3. b1: 第一步應該先將 "书樂う반" 轉成符合 zh-CN 的字集, 所以結果是 "书樂う?", (簡體字集是有包含繁體形態 "樂" 這個字的), 第二個參數 VbStrConv.TraditionalChinese, 所以結果變成 "書樂う?", 正確!
  4. b2: 正確!
  5. c1: 韓文字集不太了解, 從結果推測韓文的漢字集如果沒有 "书" 這個字的話, 結果應該算是正確的!
  6. c2: 從 c1 的結果本來預期應該得到 "?乐う반", 可是結果卻是 "??う반", 不如預期!
  7. d1: 咦!!! 怎麼會這樣, 完全不如預期, 竟然得到如此漂亮的結果, 本來預期是 4 個 "?" 的!!!
  8. d2: 一樣得到令人搞不清楚為什麼美麗結果!!!

這到底是怎麼一回事? 是假設錯誤嗎? 可是還有什麼別的可能嗎? 為了解開這個謎團, 於是又祭出了殺手工具 "Reflector", 仔細觀察了 Microsoft.VisualBasic.dll 內的程式碼, 終於了解箇中奧秘!

先來看一下 StrConv 方法反向工程之後的一小部分程式碼(還沒到重點, 所以只節錄最後幾行),

image

再來追進 vbLCMapString 看一看, 也是看下半部就行了:

image

橘黃色是和 Encoding 相關的程式碼, 綠色紅色底線的部分是 Win32 API 用來處理字碼轉換的函式, 綠色底線的函式有一個後綴字 A, 而且輸入的參數是 byte[], 而紅色底線部分的函式則沒有後綴字, 輸入的參數是 string.

看到這兒, 答案已經呼之欲出了, 之所以結果會不如預期都是因為 encoding.GetBytes() 和 encoding.GetString() 這兩個方法給弄砸的, 如果可以跳過它們直接叫用底下畫紅線的 UnsafeNativeMethods.LCMapString 的話, 就不會有那些討厭的問號產生了, 那要怎麼樣才能避過那段我們不想要的程式碼呢? 看一下那個底下有畫虛線的部分 "encoding.IsSingleByte", 嗯! 沒錯, 這就是為什麼 d1, d2 的結果這麼令人驚訝的原因了, 因為 en 的 Encoding 就是 SingleByte 所以會直接跳過 Unicode 和 MBCS 互轉的部分, 而直接進行 Unicode 的轉碼, 於是得到美麗的答案, 整個過程分析完畢!

雖然已經知道整個來龍去脈, 但是如果能再了解一下那個神奇的 Win32API: LCMapString 的話, 想必觀念又可以再更清楚一些. 所以我們再來看看 LCMapString 的重點吧! 嗯~~重點在哪兒咧? 以此篇文章的需求 "簡繁轉換" 來看的話, 只有第二個參數 dwMapFlags 值得我們注意, 打開 MSDN 的文件, 透過索引找到 LCMapString 的章節, 我們可以看到以下的內容,

image

針對 Windows NT 4.0 以後的作業系統, Microsoft 已經早就幫程式設計師們準備好了一個現成的系統函式來達成簡繁轉換的工作了(唉! 為什麼沒有早點知道!), 看你是要轉簡體 (LCMAP_SIMPLIFIED_CHINESE), 或是轉繁體 (LCMAP_TRADITIONAL_CHINESE), 只要給個參數, 一切就搞定了, 就是這麼簡單!

 

結論

如果您的需求和我一樣, 只是想把文字內容的簡繁部分轉換, 並不是想轉成 big5 或 gb, 整個輸出入都是 unicode, 而且也不想破壞其他非簡繁文字部分的話, 那麼結論就是照著本篇文章的一開始的 d1, d2 範例呼叫 VB 的 Strings.StrConv 帶上 0x0009 或是其他 SingleByte 字集的 localeID 當成第三個參數就可以啦!!!

如果不想引入 Microsoft.VisualBasic.dll (別問為什麼, 純屬個人偏好) 又想要做到相同的效果, 做法也很簡單, 請參考以下的範例程式碼!!!

public static class ChineseStringUtility
{
    internal const int LOCALE_SYSTEM_DEFAULT = 0x0800;
    internal const int LCMAP_SIMPLIFIED_CHINESE = 0x02000000;
    internal const int LCMAP_TRADITIONAL_CHINESE = 0x04000000;

    [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
    internal static extern int LCMapString(int Locale, int dwMapFlags, string lpSrcStr, int cchSrc, [Out] string lpDestStr, int cchDest);

    public static string ToSimplified(string source)
    {
        String target = new String(' ', source.Length);
        int ret = LCMapString(LOCALE_SYSTEM_DEFAULT, LCMAP_SIMPLIFIED_CHINESE, source, source.Length, target, source.Length);
        return target;
    }

    public static string ToTraditional(string source)
    {
        String target = new String(' ', source.Length);
        int ret = LCMapString(LOCALE_SYSTEM_DEFAULT, LCMAP_TRADITIONAL_CHINESE, source, source.Length, target, source.Length);
        return target;
    }
}

2007-12-18

列舉子資料夾的方法(.NET)

簡單, 就 Directory.GetDirectories() 就好啦, 但是如果要列舉的對象有著為數眾多(比如說有好幾萬)的子資料夾或是檔案的話, 那可有得等了, 而且還要有充足的記憶體供它使用, 因為它的回傳值是 string[], 所以如果有類似的需求的話, 只能另想它法囉!

當下想到的就是 WIN32 API 的 FindFirstFile, FindNextFile 函式, 可是要在 .NET 環境中使用的話, 還是稍稍封裝一下比較好, 所以第一步當然是要準備那些 DllImport 的宣告, 和最煩人的 WIN32_FIND_DATA 資料結構囉, 自己參考文件照樣重新製作一個當然也是可以, 可是如果有現成的話該多好!

先用 Reflector 參考一下 Directory.GetDirectories 是怎麼寫的, 它既然要列舉資料夾, 應該也一定會用到那些 API 才對, 果然! 相關的宣告如下:

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr FindFirstFile(string fileName, [In, Out] WIN32_FIND_DATA data);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool FindNextFile(IntPtr hndFindFile, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_DATA lpFindFileData);

[DllImport("kernel32.dll")]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
private static extern bool FindClose(IntPtr handle);
[Serializable, StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto), BestFitMapping(false)]
private class WIN32_FIND_DATA
{
    internal int dwFileAttributes;
    internal int ftCreationTime_dwLowDateTime;
    internal int ftCreationTime_dwHighDateTime;
    internal int ftLastAccessTime_dwLowDateTime;
    internal int ftLastAccessTime_dwHighDateTime;
    internal int ftLastWriteTime_dwLowDateTime;
    internal int ftLastWriteTime_dwHighDateTime;
    internal int nFileSizeHigh;
    internal int nFileSizeLow;
    internal int dwReserved0;
    internal int dwReserved1;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
    internal string cFileName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
    internal string cAlternateFileName;
}

接下來要就要自己實作一個回傳 IEnumerable 的方法, 每列舉一次只回傳一筆結果, 既可避免等待的問題, 又可以隨時停止, 如果在 .NET 1.x 的時代, 要實作一個 IEnumerable 比較麻煩, 但是 .NET 2.0 因為有 yield 這個新元素可以使用, 所以問題就簡化到一個不可思議的地步, 所以先實作一個內部使用的 internalGetFileDirectoryNames 的方法, 如下:

private const int FILE_ATTRIBUTE_DIRECTORY = 0x10;
private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
private static IEnumerable internalGetFileDirectoryNames(string path, string searchPattern, bool includeFiles, bool includeDirs, SearchOption searchOption)
{
    WIN32_FIND_DATA data = new WIN32_FIND_DATA();
    IntPtr findHandle = FindFirstFile(Path.Combine(path, searchPattern), data);

    if (findHandle == INVALID_HANDLE_VALUE) yield break;

    try
    {
        List<string> dirs = new List<string>();
        do
        {
            bool isDirectory = (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
            if (data.cFileName != "." && data.cFileName != "..")
            {
                if (isDirectory && searchOption == SearchOption.AllDirectories) dirs.Add(data.cFileName);

                if ((isDirectory && includeDirs) || (!isDirectory && includeFiles))
                {
                    yield return Path.Combine(path, data.cFileName);
                }
            }
        }
        while (FindNextFile(findHandle, data));

        foreach (string dir in dirs)
        {
            foreach (string f in internalGetFileDirectoryNames(Path.Combine(path, dir), searchPattern, includeFiles, includeDirs, searchOption))
                yield return Path.Combine(path, f);
        }
    }
    finally
    {
        bool r = FindClose(findHandle);
        System.Diagnostics.Debug.Assert(r);
    }
}

最後, 模擬原生的 Directory.GetFiles, Directory.GetDirectories, Directory.GetFileSystemEntries 函式簽名, 自己 overload 一下就搞定啦, 以下列出最多屬性的函式宣告:

public static IEnumerable GetFiles(string path, string searchPattern, SearchOption searchOption)
{
    return internalGetFileDirectoryNames(path, searchPattern, true, false, searchOption);
}
public static IEnumerable GetDirectories(string path, string searchPattern, SearchOption searchOption)
{
    return internalGetFileDirectoryNames(path, searchPattern, false, true, searchOption);
}
public static IEnumerable GetFileSystemEntries(string path, string searchPattern, SearchOption searchOption)
{
    return internalGetFileDirectoryNames(path, searchPattern, true, true, searchOption);
}

完成啦! 完整的程式碼在這裡!

2007-11-30

Visual Studio 2008 在 Vista x64 上不能 Edit and Continue?

Visual Studio 在 x64 的系上不支援 Edit and Continue(E&C)? 真的嗎?

image

今天在 x64 Vista 上的 VS2008 要 debug 程式的時候, 一直被警告必須重新編譯程式, 原本以為是因為 lambda expression 或是 anonymous function 的關係, 後來寫了一個更簡單的類別測試了一下, 發現還是不行, 只好向 Google 求助!

原來是 64-bit CLR 不支援 E&C, 但是只要編譯時選擇 x86 平台的話, 就可以再啟動 E&C 的功能囉!!

怎麼設定呢? 在 Project 上點右鍵, Properties/Build, 把 Platform Target 設成 x86 就行啦!

image

2007-08-08

C# 3.0 筆記: Automatic Properties, Object Initializers, Collection Initializers

1. Automatic Properties 2. Object Initializers 3. Collection Initializers 1. Automatic Properties

//=== BEFORE ===
public class Person {
    private string _name;
    private int _age;

    public string Name {
        get { return _name; }
        set { _name = value; }
    }

    public int Age {
        get { return _age; }
        set { _age = value; }
    }
}
//=== AFTER ===
public class Person {
    public string Name { get; set; }
    public int Age { get; set; }
}
2. Object Initializers
//=== BEFORE ===
Person p = new Person();
p.Name = "John";
p.Age = 30;
//=== AFTER ===
Person p = new Person { Name = "John", Age = 30 };
3. Collection Initializers
//=== Way 1 ===
List ps = new List();

ps.Add(new Person { Name = "John", Age = 30 });
ps.Add(new Person { Name = "Mary", Age = 25 });
//=== Way 2 ===
List ps = new List {
    new Person { Name = "John", Age = 30 },
    new Person { Name = "Mary", Age = 25 };