2017年2月3日 星期五

網頁離線存取IndexedDB

因為客戶的需求必須要有離線做資料登打,後續再做資料上傳,雖然最後又不要使用,但我還是研究了一下把做法記下來
  1. 在HTML頁面上的html tag 裡加上manifest="~/cache.appcache",這個檔案是參考亞斯狼的教學,利用MVC去產生需要被快取的檔案,包含html、js、css,結構如下
    CACHE MANIFEST
    #V1.0

    需要快取檔案的相對路徑

    NETWORK:
    需要連線的檔案相對路徑

    FALLBACK:
    允許將用戶轉入一個指定的頁面
  2.  原則上只要簡單的頁面上面這樣設定就可以了,那如果要應付較複雜的表單就要在利用前端的資料存取,最簡單的就是使用LoaclStorage及SessionStorage,相容性也高,離線存取用LocalStorage比較適合,畢竟可以一直存在電腦裡,SessionStorage只要關掉瀏覽器資料就會清掉了,但因本次的專案有比較複雜,要有模擬AJAX取資料的動作,因為Storage只能存字串,再某些情況下變得很不好用,所以這邊我用IndexedDB來當我的前台資料庫。
  3.  IndexedDB原則上是非同步的方式在處理資料,一開始在寫他的方法時還遇到蠻多問題,對JS的非同步不太熟^_^;;;,程式碼如下,基本上簡單的資料庫存取都可以。
    var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
    var IDBTransaction = window.IDBTransaction || window.webkitTransaction || window.msIDBTransaction || { READ_WRITE: "readwrite" };
    var AccountDB;
    //
    
    function DBClass(tableName, columns) {
        this.TableName = tableName;
        this.Columns = columns;
        this.DBObject = undefined;
    }
    
    DBClass.prototype.CreateTable = function () {
        var that = this;
        var request = indexedDB.open(that.TableName);
        //
        request.onsuccess = function () {
            that.DBObject = request.result;
        };
        request.onerror = function (event) {
            console.log("IndexedDB error: " + event.target.errorCode);
        };
        request.onblocked = function (event) {
            console.log("IndexedDB on blocked");
            event.target.result.close();
        };
        request.onupgradeneeded = function (event) {
            var hasKey;
            var objectStore;
            //var tableName = that.TableName;
            //
            if (that.Columns) {
                hasKey = that.Columns.some(function (element, index, array) {
                    if (element.Key) {
                        objectStore = event.currentTarget.result.createObjectStore(that.TableName, {
                            keyPath: element.ColumnName, autoIncrement: true
                        });
                        return true;
                    } else {
                        return false;
                    }
                });
                if (!hasKey) {
                    objectStore = event.currentTarget.result.createObjectStore(that.TableName, {
                        keyPath: "SerialNumber", autoIncrement: true
                    });
                }
                that.Columns.forEach(function (element, index, array) {
                    if (!element.Key) {
                        objectStore.createIndex(element.ColumnName, element.ColumnName, { unique: element.Unique });
                    }
                });
            }
        };
    };
    
    DBClass.prototype.AddData = function (columnsValueArray) {
        var transaction = this.DBObject.transaction(this.TableName, "readwrite");
        var objectStore = transaction.objectStore(this.TableName);
        var request;
        var columnsObject = {};
        columnsValueArray.forEach(function (element, index, array) {
            columnsObject[element.ColumnName] = element.Value;
        });
        request = objectStore.add(columnsObject);
        //
        request.onsuccess = function (event) {
            console.log("data save success!");
        };
        request.onerror = function (event) {
            console.log("data save error!");
        };
    };
    
    DBClass.prototype.UpdateData = function (columnsValueArray) {
        var transaction = this.DBObject.transaction(this.TableName, "readwrite");
        var objectStore = transaction.objectStore(this.TableName);
        var request;
        var columnsObject = {};
        columnsValueArray.forEach(function (element, index, array) {
            columnsObject[element.ColumnName] = element.Value;
        });
        request = objectStore.put(columnsObject);
        //
        request.onsuccess = function (event) {
            console.log("data update success!");
        };
        request.onerror = function (event) {
            console.log("data update error!");
        };
    };
    
    DBClass.prototype.DeleteData = function (keyValue) {
        var transaction = this.DBObject.transaction(this.TableName, "readwrite");
        var objectStore = transaction.objectStore(this.TableName);
        var request;
        request = objectStore.delete(keyValue);
        request.onsuccess = function (event) {
            console.log("data delete success!");
        };
        request.onerror = function (event) {
            console.log("data delete error!");
        };
    };
    
    DBClass.prototype.GetData = function (keyValue, getFunc) {
        var transaction = this.DBObject.transaction(this.TableName);
        var objectStore = transaction.objectStore(this.TableName);
        var request;
        request = objectStore.get(keyValue);
        objectStore.op
        //
        request.onsuccess = function (event) {
            if (getFunc) {
                getFunc(event.target.result);
            }
        };
        request.onerror = function (event) {
            console.log("data search error!");
        };
    };
    
    DBClass.prototype.GetAll = function (getFunc) {
        var transaction = this.DBObject.transaction(this.TableName);
        var objectStore = transaction.objectStore(this.TableName);
        var cursor = objectStore.openCursor();
        //
        cursor.onsuccess = function (event) {
            if (getFunc) {
                getFunc(event.target.result);
            }
        };
    };
    
    DBClass.prototype.DropTable = function () {
        if (this.DBObject) {
            this.DBObject.close();
        }
        var request = indexedDB.deleteDatabase(this.TableName);
        request.onsuccess = function () {
            console.log("Delete database successfully");
        };
        request.onerror = function () {
            console.log("Delete database error");
        };
        request.onblocked = function (event) {
            console.log("Database is on blocked, it's can't delete");
        };
    };
    
    var ColumnInfoClass = function (columnName, key, unique) {
        this.ColumnName = columnName;
        this.Key = key === undefined ? false : key;
        this.Unique = unique === undefined ? false : unique;
    };
    
    var ColumnValueClass = function (columnName, value) {
        this.ColumnName = columnName;
        this.Value = value;
    };
    
    function TestDB() {
        var columns = [];
        columns.push(new ColumnInfoClass("ID", true, true));
        columns.push(new ColumnInfoClass("Name"));
        columns.push(new ColumnInfoClass("Phone"));
        AccountDB = new DBClass("Account", columns);
        AccountDB.CreateTable();
    }
    
    function AddDB() {
        var values = [];
        values.push(new ColumnValueClass("ID", 1));
        values.push(new ColumnValueClass("Name", "測試新增"));
        values.push(new ColumnValueClass("Phone", "123456"));
        //
        AccountDB.AddData(values);
    }
    
    function UpdateDB() {
        var values = [];
        values.push(new ColumnValueClass("ID", 1));
        values.push(new ColumnValueClass("Name", "測試修改"));
        values.push(new ColumnValueClass("Phone", "654321"));
        //
        AccountDB.UpdateData(values);
    }
    
    function DeleteDB() {
        AccountDB.DeleteData(1);
    }
    
    function GetDB() {
        AccountDB.GetData(1, function (request) {
            if (request) {
                console.log(request.Name + "_" + request.Phone);
            } else {
                console.log("No Data!");
            }
        });
    }
    
    function GetAll() {
        AccountDB.GetAll(function (request) {
            if (request) {
                console.log(request.value.Name);
                request.continue();
            }
        });
    }
    

2016年9月23日 星期五

深入理解C#(C# in Depth) 讀書心得 Part 3

Part 4 C# 4.0 良好的交互性(Playing nicely with others)
  1. 簡化程式碼的微小修改
    •  可選參數(optional parameter)及命名參數(named argument),可選參數就是在方法的參數上可指派預設值達到可選的效果,不用像以前必須要做方法的覆寫,命名參數是在呼叫方法時傳入參數(arguments)時指定對應到方法參數(parameters)的名稱,這樣可不必對應參數位置
      • 可選參數的使用,看以下範例,y及z皆有預設值所以可當可選參數
      • static void Dump(int x, int y = 20, int z = 30)
        {
           Console.WriteLine("x={0} y={1} z={2}", x, y, z);
        }
        Dump(1, 2, 3);
        Dump(1, 2);
        Dump(1);
        
      • 原則上可選參數的值要為常數,可以為null的型別,可選參數一定要在最後面
      • 命名參數使用,看以下範例,有一些組合的方式
      • static void Dump(int x, int y, int z)
        {
           Console.WriteLine("x={0} y={1} z={2}", x, y, z);
        }
        Dump(1, 2, 3);
        Dump(x: 1, y: 2, z: 3);
        Dump(z: 3, y: 2, x: 1);
        Dump(1, y: 2, z: 3);
        Dump(1, z: 3, y: 2);
        
      • 命名參數要注意到順序,雖然他不必對應參數位置,但為了可讀性還是依照順序寫比較好
      • 前面兩種方法把它結合在一起的話呢,原則上沒太大的問題,主要要注意到使用時會不會造成參數模擬兩可的情形發生,或是型別上的問題,比如以下範例,第一個要注意如果都不帶參數則兩個都可執行,第二個範例要注意object與int的型別轉換
      • //範例1
        static void Foo(int x = 10) {}
        static void Foo(int x = 10, int y = 20) {}
        
        Foo();
        Foo(1);
        Foo(y: 2);
        Foo(1, 2);
        //
        //範例2
        void Method(int x, object y) { ... }
        void Method(object a, int b) { ... }
        
        Method(10, 10);
        //可用這兩種方法解決
        Method(10, (object) 10);
        Method(x: 10, y: 10);
        
    • 改善COM的互動,這部分較沒有涉獵到,主要也是參數的改變,書上以操作Word文件檔為例子,以往在產生一個Word文件檔時要傳入一些不管有沒有用到的參數,C#4.0因有可選參數及命名參數讓這部分的程式碼精簡很多而且可讀性也較高。
    • 介面及委派的泛型的可變性,也就是共變數與反變數
      • 共變數(covariance)是回傳一個方法的值,如下範例
      • interface IFactory<T>
        {
           T CreateInstance();
        }
        
      • 反變數(contravariance)是傳入值並處理這個值,如下範例
      • interface IPrettyPrinter<T>
        {
           void Print(T document);
        }
        
      • 不變數(invariance)是指非共變或反變,雙向傳遞值,如下範例是進行序列化及反序列化
      • interface IStorage<T>
        {
           byte[] Serialize(T value);
           T Deserialize(byte[] data);
        }
        
      • 介面的共變數的寫法如下範例,shapesByConcat將兩個類別項目結合在一起
      • //Circle及Square都實做ISharp介面
        List<Circle> circles = new List<Circle> {
            new Circle(new Point(0, 0), 15),
            new Circle(new Point(10, 5), 20),
        };
        List<Square> squares = new List<Square> {
            new Square(new Point(5, 10), 5),
            new Square(new Point(-10, 0), 2)
        };
        //
        List<ISharp> shapesByAdding = new List<ISharp>();
        shapesByAdding.AddRange(circles);
        shapesByAdding.AddRange(squares);
        List shapesByConcat = circles.Concat(squares).ToList();
        
      • 介面的反變數寫法如下範例,circles可以用ISharp的類別做排序
      • class AreaComparer : IComparer<ISharp>
        {
            public int Compare(IShape x, IShape y)
            {
                return x.Area.CompareTo(y.Area);
            }
        }
        IComparer<ISharp> areaComparer = new AreaComparer();
        circles.Sort(areaComparer);
        
      •  委派中的可變性的寫法如下範例
      • //共變數
        Func<Square> squareFactory = () => new Square(new Point(5, 5), 10);
        Func<IShape> shapeFactory = squareFactory;
        //反變數
        Action<IShape> shapePrinter = shape => Console.WriteLine(shape.Area);
        Action<Square> squarePrinter = shapePrinter;
        //
        squarePrinter(squareFactory());
        shapePrinter(shapeFactory());
        
      •  較複雜的情況是同時有共變數及反變數的情形下如何實做,來看Converter<TInput, TOutput>這個類別,這跟Func<T, Tresult>類似,來看以下範例
      • Converter<object, string> converter = x => x.ToString();
        Converter<string, string> contravariance = converter;
        Converter<object, object> covariance = converter;
        Converter<string, object> both = converter;
        
    •  這邊有提到Lock在C# 4.0有一些改良,主要是避免在執行中發生錯誤導致執行緒被咬住的情況。
  2. 靜態語言中的動態綁定
    • 動態類型(dynamic)是什麼、何時用、為何而用、如何用
      • 動態類型就是在執行時期才決定型別
      • 動態類型在不確定型別但有相同屬性時可以使用,例如Length屬性,不管是String、StringBuilder、Array、Stream都不重要,只要能取得Length就好
    •  動態型別的用法就是dynamic這個關鍵字,基本上他會自動做隱含轉換為CLR類型,來看幾個範例
    • //這邊valueToAdd在item相加時會隱含轉換成字串,結果就是First2,Second2,Third2
      dynamic items = new List<string> { "First", "Second", "Third" };
      dynamic valueToAdd = 2;
      foreach (dynamic item in items)
      {
         string result = item + valueToAdd;
         Console.WriteLine(result);
      }
      //這個範例在執行中會報Microsoft.CSharp.RuntimeBinder.RuntimeBinderException的錯誤,因為這並非預期中的轉換
      dynamic items = new List<int> { 1, 2, 3 };
      dynamic valueToAdd = 2;
      foreach (dynamic item in items)
      {
         string result = item + valueToAdd;
         Console.WriteLine(result);
      }
      //只要把string result = item + valueToAdd改成
      Console.WriteLine(item + valueToAdd);
      
    • 動態型別的一些範例,目前有幾個類別庫是使用動態型別做的,包含MassiveDapperJson.NET
      • ˋ這邊作者有用Office.Excel的元件做範例,在之前我們都會用靜態類別然後強制轉型,如下第一個範例,現在我們可以改用dynamic的方式如下第二個範例 
      • //before
        var app = new Application { Visible = true };
        app.Workbooks.Add();
        Worksheet worksheet = (Worksheet) app.ActiveSheet;
        Range start = (Range) worksheet.Cells[1, 1];
        Range end = (Range) worksheet.Cells[1, 20];
        worksheet.Range[start, end].Value = Enumerable.Range(1, 20).ToArray();
        //after
        var app = new Application { Visible = true }; app.Workbooks.Add();
        dynamic worksheet = app.ActiveSheet;
        dynamic start = worksheet.Cells[1, 1];
        dynamic end = worksheet.Cells[1, 20]; worksheet.Range[start, end].Value = Enumerable.Range(1, 20).ToArray();
        
      • 再來作者提到使用IronPython,他可以很方便的去引用Python,這部分比較沒有涉獵來看個範例知道一下
      • string python = @"
           text = 'hello'
           output = input + 1
           ";
        ScriptEngine engine = Python.CreateEngine(); 
        ScriptScope scope = engine.CreateScope(); 
        scope.SetVariable("input", 10); 
        engine.Execute(python, scope); 
        Console.WriteLine(scope.GetVariable("text")); 
        Console.WriteLine(scope.GetVariable("input")); 
        Console.WriteLine(scope.GetVariable("output"));
        
      • 執行時的類型判斷,如果你想要做的不是只有調用方法,罵麼最好將所有額外的工作包在一個泛型方法內,然後動態的調用該泛型方法,用靜態類型來編寫所有剩餘的程式碼,如下範例
      • private static bool AddConditionallyImpl<T>(IList<T> list, T item) {
        if (list.Count < 10)
           {
              list.Add(item);
              return true;
           }
           return false;
        }
        public static bool AddConditionally(dynamic list, dynamic item)
        {
           return AddConditionallyImpl(list, item);
        }
        object list = new List<string> { "x", "y" };
        object item = "z";
        AddConditionally(list, item);
        
      • 彌補泛型運算式的不足,如下範例,有兩點比較有趣的事用default(T)來初始化total,另一個是將相加的結果強制轉換回T
      • public static T DynamicSum<T>(this IEnumerable<T> source)
        {
           dynamic total = default(T);
           foreach (T element in source)
           {
              total = (T) (total + element);
           }
           return total;
        }
        byte[] bytes = new byte[] { 1, 2, 3 };
        Console.WriteLine(bytes.DynamicSum());
        //印出6
        
      • 鴨子類型(Duck Typing),我們知道在執行時可以使用某個具有特殊名稱的成員,但無法確切告訴編譯器這個成員,因為這將取決於具體的類型,鴨子類型允許我們在訪問Count時不必執行類型檢查,如下範例
      • static void PrintCount(IEnumerable collection)
        {
           dynamic d = collection;
           int count = d.Count;
           Console.WriteLine(count);
        }
        ...
        PrintCount(new BitArray(10));
        PrintCount(new HashSet<int> { 3, 5 });
        PrintCount(new List<int> { 1, 2, 3 });
        
      • 多重分發,靜態類型是單一分發(single dispatch)的,多重分法會根據執行時參數的類型找出最適合的方法實現,如下範例
      • private static int CountImpl<T>(ICollection<T> collection)
        {
            return collection.Count;
        }
        private static int CountImpl(ICollection collection)
        {
            return collection.Count;
        }
        private static int CountImpl(string text)
        {
            return text.Length;
        }
        private static int CountImpl(IEnumerable collection)
        {
            int count = 0;
            foreach (object item in collection)
            {
                count++; 
            }
            return count;
        }
        public static void PrintCount(IEnumerable collection)
        {
            dynamic d = collection;
            int count = CountImpl(d);
            Console.WriteLine(count);
        }
        PrintCount(new BitArray(5));
        PrintCount(new HashSet<int> { 1, 2 });
        PrintCount("ABC");
        PrintCount("ABCDEF".Where(c => c > 'B'));
        
    • 背後的原理
      • DLR本身只是一個類別庫,他與CLR是不在同一個層級,另一個重要的部分是他有多層級暫存,這攸關到它的效能
      • DLR的核心概念
        1. 調用點-引用他的地方
        2. 接收器與綁定器-它需要其他訊息來判斷程式碼的含義及如何執行,接收器就是執行時接收引用的變數,綁定器(Binder)取決於語言,這邊C#是引用Microsoft.CSharp.RuntimeBinder.Binder
        3. 規則和緩存-規則就是調用所做出的決策,規則也關係到優化,再來會把規則儲存在緩存中
      • C#編譯器如何處理動態
        1. 如果使用了動態,他就是動態,這不是廢話嗎,他想表達的是當宣告為dynamic就一定會找尋符合dynamic對應的方法,如下範例
        2. static void Execute(string x)
          {
             Console.WriteLine("String overload");
          }
          static void Execute(dynamic x)
          {
             Console.WriteLine("Dynamic overload");
          }
          dynamic text = "text";
          Execute(text);  //執行string 參數的方法
          dynamic number = 10;
          Execute(number);  //執行dynamic 參數的方法
          
        3. CLR類型與動態類型之間的轉換,如果兩個類型(比如說dynamic與string)可以進行雙向的隱含轉換,情況會變得很糟糕,來看下面範例,array應該是什麼類別,他是dynamic[]而不是string[],編譯器可以將string轉換為dynamic但反過來就不行
        4. dynamic d = 0;
          string x = "text";
          var array = new[] { d, x };
          
        5. 動態運算式不一定都是動態求值,如下範例可以使用as來轉換型別
        6. dynamic d = GetValueDynamically();
          string x = d as string;
          
        7. 動態運算式不一定都是動態類型,如下範例
        8. dynamic d = GetValueDynamically();
          SomeType x = new SomeType(d);
          
      • 更加智能的C#編譯器,裡面提到了像多載裡有宣告dynamic時會對應到哪個多載的方法,這部分有些情況編譯器會在執行前就檢測出有問題的部分,其實這些在撰寫過程還是要注意型別轉換時是否會出現轉換失敗或對應不到相對應的方法。
      • 動態程式碼的約束
        1. 不能動態處理擴充方法,傳入的參數必須要轉型成靜態類型,如下範例
        2. dynamic size = 5;
          var numbers = Enumerable.Range(10, 10);
          var error = numbers.Take(size);  //這會編譯錯誤
          //下面這兩段可以編譯成功雖然有點醜
          var workaround1 = numbers.Take((int) size);
          var workaround2 = Enumerable.Take(numbers, size);
          
        3. 委派與動態類型之間的轉換限制,這邊展示了一些可以與不可以的用法
        4. //這邊都是允許的
          dynamic badMethodGroup = Console.WriteLine;
          dynamic goodMethodGroup = (Action<string>) Console.WriteLine;
          dynamic badLambda = y => y + 1;
          dynamic goodLambda = (Func<int, int>) (y => y + 1);
          dynamic veryDynamic = (Func<dynamic, dynamic>) (d => d.SomeMethod());
          //下面這段就編譯不過
          void Method(Action<string> action, string value)
          {
             action(value);
          }
          dynamic text = "error";
          Method(x => Console.WriteLine(x), text);
          
        5. 查詢動態元素的集合,下面範例會把number認定為int
        6. var list = new List<dynamic> { 50, 5m, 5d };
          var query = from number in list
                      where number > 4
                      select (number / 20) * 10;
          foreach (var item in query)
          {
             Console.WriteLine(item);
          }
          //這邊會印出20,2.50,2.5
          
        7. 類型指定和泛型類別參數,如下範例指出哪些可以哪些不行
        8. //下面四行是編譯不過的
          class BaseTypeOfDynamic : dynamic
          class DynamicTypeConstraint<T> where T : dynamic
          class DynamicTypeConstraint<T> where T : List<dynamic>
          class DynamicInterface : IEnumerable<dynamic>
          //下面兩行是允許的
          class GenericDynamicBaseClass : List<dynamic>
          IEnumerable<dynamic> variable;
          
      • 實現動態行為,這個部分日後有興趣再來看這部分。
Part 5 C# 5.0 讓非同步更加簡單
  1. 用async/await實現非同步
    • 介紹非同步的方法
      • 這邊用一個簡單的範例做一個介紹,使用HTTPClient的非同步方法,如果在之前可以使用WebClient來達到同樣效果但差別在於他不是非同步
      • class AsyncForm : Form
        {
           Label label;
           Button button;
           public AsyncForm()
           {
              label = new Label { Location = new Point(10, Text = "Length" };
              button = new Button { Location = new Point(10, 50), Text = "Click" };
              button.Click += DisplayWebSiteLength;
              AutoSize = true;
              Controls.Add(label);
              Controls.Add(button);
           }
           async void DisplayWebSiteLength(object sender, EventArgs e)
           {
              label.Text = "Fetching...";
              using (HttpClient client = new HttpClient())
              {
                 string text = await client.GetStringAsync("http://csharpindepth.com");
                 label.Text = text.Length.ToString();
              }
           }
        }
        
      • 再來改寫一下DisplayWebSiteLength方法,使用task類別來接值,如下範例,task在await回傳單純就是string
      • async void DisplayWebSiteLength(object sender, EventArgs e)
        {
            label.Text = "Fetching...";
            using (HttpClient client = new HttpClient())
            {
                Task<string> task = client.GetStringAsync("http://csharpindepth.com");
                string text = await task;
                label.Text = text.Length.ToString();
            }
        }
        
    • 非同步的思考方式
      • 說明了非同步在程式上的執行順序是什麼,這邊基本上就是思考有多執行緒在執行其他段落
      • 非同步的方法寫法,如下範例,GetPageLengthAsync是非同步方法,呼叫方法及非同步方法中間的分隔是Task<int>,非同步方法及非同步的運算式的分隔是Task<string>
      • static async Task<int> GetPageLengthAsync(string url)
        {
           using (HttpClient client = new HttpClient())
           {
              Task<string> fetchTextTask = client.GetStringAsync(url);
              int length = (await fetchTextTask).Length;
              return length;
           } 
        }
        static void PrintPageLength()
        {
           Task<int> lengthTask = GetPageLengthAsync("http://csharpindepth.com");
           Console.WriteLine(lengthTask.Result);
        }
        
    • 語法和語意
      • 宣告一個非同步方法時,只要在回傳型別前面任何地方加上async就可以,為了一致性統一放在回傳型別的前一個位置
      • async的回傳型別有3種
        1. void
        2. Task
        3. Task<TResult>
      • await基本上就是放在運算式前面
      • 流暢的await運算式
        1. 來看稍微複雜一點的await,下面兩個範例的結果是一樣的
        2. //不透過Task直接取值
          string pageText = await new HttpClient().GetStringAsync(url);
          //透過Task來暫存值
          Task task = new HttpClient().GetStringAsync(url); 
          string pageText = await task;
          
        3. 有一種狀況有潛在的效能問題,如下範例,這部分要考慮是否使用並行的方式來處理
        4. AddPayment(await employee.GetHourlyRateAsync() *
                     await timeSheet.GetHoursWorkedAsync(employee.Id));
          
      • 例外處理
        1. 例外的部分原則上會拋出AggregateException,要注意到的是如有多個非同步執行的例外,他只會返回一個,如要接回多個例外可寫擴充方法,如下範例
        2. public static AggregatedExceptionAwaitable WithAggregatedExceptions(this Task task)
          {
              return new AggregatedExceptionAwaitable(task);
          }
          
          // In AggregatedExceptionAwaitable
          public AggregatedExceptionAwaiter GetAwaiter()
          {
              return new AggregatedExceptionAwaiter(task);
          }
          
          // In AggregatedExceptionAwaiter
          public bool IsCompleted
          {
              get { return task.GetAwaiter().IsCompleted; }   //❶ 委托给任务awaiter
          }
          
          public void OnCompleted(Action continuation)
          {
              task.GetAwaiter().OnCompleted(continuation);   //❶ 委托给任务awaiter
          }
          
          public void GetResult()
          {
              task.Wait();   //❷ 发生错误时,直接抛出AggregateException
          }
          
          private async static Task CatchMultipleExceptions()
          {
              Task task1 = Task.Run(() => { throw new Exception("Message 1"); });
              Task task2 = Task.Run(() => { throw new Exception("Message 2"); });
              try
              {
                  await Task.WhenAll(task1, task2).WithAggregatedExceptions();
              }
              catch (AggregateException e)
              {
                  Console.WriteLine("Caught {0} exceptions: {1}",
                      e.InnerExceptions.Count,
                      string.Join(", ",
                          e.InnerExceptions.Select(x => x.Message)));
              }
          }
          
        3. 任務本身也可以做取消,任務本身有一個狀態的屬性可以查詢,參考下面範例,另外還有一些狀況在遇到問題時可再了解例外更進階的處理
        4. static async Task DelayFor30Seconds(CancellationToken token)
          {
              Console.WriteLine("Waiting for 30 seconds...");
              await Task.Delay(TimeSpan.FromSeconds(30), token);   //❶ 启动一个异步的延迟操作
          }
          ...
          var source = new CancellationTokenSource();
          var task = DelayFor30Seconds(source.Token);   //❷ 调用异步方法
          source.CancelAfter(TimeSpan.FromSeconds(1));
          Console.WriteLine("Initial status: {0}", task.Status);   //❸ 请求延迟的token取消操作
          try
          {
              task.Wait();   //❹ 等待完成(同步)
          }
          catch (AggregateException e)
          {
              Console.WriteLine("Caught {0}", e.InnerExceptions[0]);
          }
          Console.WriteLine("Final status: {0}", task.Status);   //❺ 显示任务状态
          //
          //結果如下
          Waiting for 30 seconds...
          Initial status: WaitingForActivation
          Caught System.Threading.Tasks.TaskCanceledException: A task was canceled.
          Final status: Canceled
          
    • 非同步的匿名函式,基本上是一樣的只要在前面加上async,直接看個範例就清楚了
    • Funcint<int, Task<int>> function = async x =>
      {
          Console.WriteLine("Starting... x={0}", x);
          await Task.Delay(x * 1000);
          Console.WriteLine("Finished... x={0}", x);
          return x * 2;
      };
      Task<int> first = function(5);
      Task<int> second = function(3);
      Console.WriteLine("First result: {0}", first.Result);
      Console.WriteLine("Second result: {0}", second.Result);
      //
      //會得到如下結果,要注意到的是Result會等到阻塞線程直到任務結束
      Starting... x=5
      Starting... x=3
      Finished... x=3
      Finished... x=5
      First result: 10
      Second result: 6
      
    •  編譯器的轉換,這部分牽扯到細部的講解,未來有意要深入了解時再回頭來看
    •  高效的使用async/await
      • 基於任務的非同步模式,在C#5 微軟為此定義一套標準TAP(Task base Asynchronous Pattern),完整的說明在這MSDN,作者建議要熟練的話把他看過一次會有更深的了解,以下他就說明他覺得最重要的部分
        1. 非同步方法命名的方式因怕跟原生的類別的方法衝突,建議方法名稱後綴加入TaskAsync,比如DownloadStringTaskAsync
        2. 一般來說非同步方法回傳的是Task或Task<T>,建立非同步方法時應考慮提供4種覆寫方法具有相同的基本參數,如下範例,IProgress<int>是用於進度報告
        3. //比如我們要建立下面這個方法
          Employee LoadEmployeeById(string id)
          Task<Employee> LoadEmployeeById(string id)
          Task<Employee> LoadEmployeeById(string id, CancellationToken cancellationToken)
          Task<Employee> LoadEmployeeById(string id, IProgress<int> progress)
          Task<Employee> LoadEmployeeById(string id,
              CancellationToken cancellationToken, IProgress<int> progress)
          
        4. 另外有一種方法可以不會去影響到Context,就是使用ConfigureAwait的屬性,他的參數只有一個continueOnCaptureContext(true/false),ture為正常等待會在同一個執行緒裡,false則忽略會在執行緒池裡,如以下範例是忽略上下文的部分
      • 組合非同步的操作
        1. 在單個調用中收集結果,如下範例,啟動多個請求,TPL提供了Task.WhenAll提供將各有一個結果的多個任務組合成一個包含多個結果的任務
        2. var tasks = urls.Select(async url =>
          {
              using (var client = new HttpClient())
              {
                  return await client.GetStringAsync(url);
              }
          }).ToList();
          //
          string[] results = await Task.WhenAll(tasks);
          
        3. 在全部完成時收集結果,看的不是很了他想表達什麼,這等我收集多一點的經驗及文章時再來了解
      • 對非同步程式碼寫單元測試,這部分有需要寫的時候再回頭看
  2. C# 5.0附加特性和結束語
    • foreach循環中捕捉變數的變化,在C# 3、4以下範例會輸出3個z,這總是讓人覺得很奇怪,但這也不是語言的錯誤而是它的特性,到了C# 5它會個別存變數值,所以會正常顯示x、y、z
      string[] values = { "x", "y", "z" };
      var actions = new ListAction();
      foreach (string value in values)
      {
         actions.Add(() = Console.WriteLine(value));
      }
      foreach (Action action in actions)
      {
         action(); 
      }
      
    • 調用者的訊息特性,這部分提到了3種方法來取得調用者的資訊,CallerFilePathAttribute、CallerLineNumberAttribute、CallerMemberNameAttribute,簡單看一下範例
      static void ShowInfo([CallerFilePath] string file = null,
                           [CallerLineNumber] int line = 0,
                           [CallerMemberName] string member = null)
      {
         Console.WriteLine("{0}:{1} - {2}", file, line, member);
      }
      ShowInfo();
      ShowInfo("LiesAndDamnedLies.java", -10);
      //結果
      c:\Users\Jon\Code\Chapter16\CallerInfoDemo.cs:21 - Main 
      LiesAndDamnedLies.java:-10 - Main
      

2016年8月3日 星期三

深入理解C#(C# in Depth) 讀書心得 Part 2

Part 3 C# 3.0 資料存取重大改變(Revolutionizing data access)
  1. 使用聰明的編譯器防止錯誤(Cutting fluff with a smart compiler)
    • 自動產生屬性對應的程式碼(Automatically implemented properties),就是簡化properry的撰寫,編譯器會自動產生相對應的程式碼,範例如下,這樣可以替代一些寫在class裡的區域變數
    • public string Name { get; set; }
      //編譯成
      private string k__BackingField;
      public string Name
      {
          get { return k__BackingField; }
          set { k__BackingField = value; }
      }
      
    •  宣告隱含型別的變數(Implicityping of local variables),就是可以在局部區塊用var 宣告一個變數,並指定初始值
      • 使用的幾個原則
        1. 一定要給初始值
        2. 不可指定匿名方法或方法群組 
        3. 不可指定null
      • 作者提到一些優缺點,基本上不必因為是新的寫法就拼命用,要評估程式碼的適用情形,最大的考慮點就是可讀性的好壞,比如說宣告一個Dictionary<string, List<Person>>如果沒使用var,程式碼會看起來又臭又長,但有個好處是明確定義變數的類型,這部分可以斟酌使用,並沒有絕對的好與壞。
    •  簡單的初始化(Simplified initialization)C# 3.0在類別的初始化,增加了高可讀性及簡化了程式碼的撰寫
      • 看個範例程式碼就知道改了甚麼
      • //建立一個簡單的類別
        public class Person
        {
            public int Age { get; set; }
            public string Name { get; set; }
            List<Person> friends = new List<Person>();
            public List<Person> Friends { get { return friends; } }
            Location home = new Location();
            public Location Home { get { return home; } }
            public Person() { }
            public Person(string name)
            {
                Name = name;
            }
        }
        public class Location
        {
            public string Country { get; set; }
            public string Town { get; set; }
        }
        
        //C# 2.0的寫法
        Person tom1 = new Person();
        tom1.Name = "Tom";
        tom1.Age = 9;
        Person tom2 = new Person("Tom");
        tom2.Age = 9;
        
        //C# 3.0增加的語法糖 
        Person tom3 = new Person() { Name = "Tom", Age = 9 };
        Person tom4 = new Person { Name = "Tom", Age = 9 };
        Person tom5 = new Person("Tom") { Age = 9 };
        
        //還可以這樣
        Person[] family = new Person[]
        {
            new Person { Name = "Holly", Age = 36 },
            new Person { Name = "Jon", Age = 36 },
            new Person { Name = "Tom", Age = 9 },
            new Person { Name = "William", Age = 6 },
            new Person { Name = "Robin", Age = 6 }
        };
        
    • 隱含類型的陣列(Implicitly typed arrays),更便利的方式來做陣列的宣告及使用方式,看個範例
    • //C#1.0 2.0會這樣寫
      string[] names = {"Holly", "Jon", "Tom", "Robin", "William"};
      MyMethod(names);
      //C#3.0可以這樣寫
      MyMethod(new string[] {"Holly", "Jon", "Tom", "Robin", "William"});
      //或
      MyMethod(new[] {"Holly", "Jon", "Tom", "Robin", "William"});
      
    • 匿名型別(Anonymous types),這一般來說都會結合LINQ一起使用,這邊先就他的特性稍微瞭解一下
      • 就前面的Person的範例來看,我們不先宣告Person的類別,直接使用
      • //名叫Tom 9歲
        var tom= new { Name = "Tom", Age = 9 };
        //也可以用在陣列裡
        var family = new[]
        {
            new { Name = "Holly", Age = 36 },
            new { Name = "Jon", Age = 36 },
        };
        
      • 複製屬性,可以用更簡潔的寫法
      • //原本的寫法
        new { Name = person.Name, IsAdult = (person.Age >= 18) }
        //可以省略指定屬性名,直接複製名稱及值
        new { person.Name, IsAdult = (person.Age >= 18) }
        //來看個碗整一點的範例
        List<Person> family = new List<Person>
                {
                   new Person { Name = "Holly", Age = 36 },
                   new Person { Name = "Jon", Age = 36 },
                   new Person { Name = "Tom", Age = 9 },
                   new Person { Name = "Robin", Age = 6 },
                   new Person { Name = "William", Age = 6 }
                };
                var converted = family.ConvertAll(delegate(Person person)
                   { return new { person.Name, IsAdult = (person.Age >= 18) }; }
                );
               foreach (var person in converted)
               {
                   Console.WriteLine("{0} is an adult? {1}",
                                    person.Name, person.IsAdult);
        }
        
      • 基本上這些用法大部分的會配合著LINQ來使用,可以讓程式碼更好寫更易懂,後續看到LINQ時還會在使用到,而且用的會很頻繁.
  2. Lambda運算式及運算式樹狀結構(Lambda expressions and expression trees)
    • 委派的Lambda運算式(Lambda expressions as delegates),Lambda提供更容易讀的更簡潔的程式碼
      • 介紹Func<...>委派類型,在.NET3.5有提供了Func<>()的參數最多到四個,.NET4.0提供到了17個,這個Func<>()除了提供參數進去最後會回傳一個直回來,定義如下
      • TResult Func<TResult>()
        TResult Func<T,TResult>(T arg)
        TResult Func<T1,T2,TResult>(T1 arg1, T2 arg2)
        TResult Func<T1,T2,T3,TResult>(T1 arg1, T2 arg2, T3 arg3)
        TResult Func<T1,T2,T3,T4,TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4)
        //Example
        Func<string,double,int>
        //等同
        public delegate int SomeDelegate(string arg1, double arg2)
        
      • 另外如要實現void的方式,可以使用Action<...>系列的委派
    • 使用List<T>和事件的簡單範例(Simple examples using List<T> and events),這邊用幾個簡單常見的例子看一下Lambda運算式的寫法 
      • 用一個Lambda運算式來處理一個電影列表
        class Film {
           public string Name { get; set; }
           public int Year { get; set; }
        }
        var films = new List<Film>
        {
           new Film { Name = "Jaws", Year = 1975 },
           new Film { Name = "Singing in the Rain", Year = 1952 },
           new Film { Name = "Some like it Hot", Year = 1959 }
        };
        //建立重複使用的委派方法
        Action<Film> print = film => 
            Console.WriteLine("Name={0}, Year={1}", film.Name, film.Year);
        //執行print方法
        films.ForEach(print);
        //找出年度大於1960的並列出來
        films.FindAll(film => film.Year < 1960)
             .ForEach(print);
        //排序後並列出來
        films.Sort((f1, f2) => f1.Name.CompareTo(f2.Name));
        films.ForEach(print);
        
      • FindAll的實際方法在編譯器看起來應該是這樣
      • private static bool SomeAutoGeneratedName(Film film) {
           return film.Year < 1960;
        }
        films.FindAll(new Predicate<Film>(SomeAutoGeneratedName));
        
      • 再來看一下事件是如何實作的
      • static void Log(string title, object sender, EventArgs e)
        {
           Console.WriteLine("Event: {0}", title);
           Console.WriteLine(" Sender: {0}", sender);
           Console.WriteLine(" Arguments: {0}", e.GetType());
           foreach (PropertyDescriptor prop in
                    TypeDescriptor.GetProperties(e))
           {
              string name = prop.DisplayName;
              object value = prop.GetValue(e);
              Console.WriteLine("    {0}={1}", name, value);
           } 
        }
        Button button = new Button { Text = "Click me" };
        button.Click      += (src, e) => Log("Click", src, e);
        button.KeyPress   += (src, e) => Log("KeyPress", src, e);
        button.MouseClick += (src, e) => Log("MouseClick", src, e);
        
    • 表達樹(運算式樹狀結構)(Expression trees),.NET 3.5提供了一種抽象的方式將一些代碼表示成一個對象樹,Expression trees對LINQ有這很重要的關聯
      • Expression包含了兩種屬性
        1. Type-就是返回的類型,如果要反回string.length,返回的就是int.
        2. NodeType-返回所代表的表達式種類.相關詳細的描述在MSDN有完整的類別說明,原則上就是要執行什麼樣的expression就用相對應的類別.
      • 我們來建立一個簡單的Expression trees,只是單純的兩個數字相加,要注意到的是由下而上來建立運算式的,這是因為運算式是不可變的(immutable)
      • Expression firstArg = Expression.Constant(2);
        Expression secondArg = Expression.Constant(3);
        Expression add = Expression.Add(firstArg, secondArg);
        Console.WriteLine(add);
        //這邊會輸出(2 + 3)
        
      • LambdaExpression 是從Expression衍生的類別,Expression<TDelegate>又是LambdaExpression的衍生類別,Expression<TDelegate>以靜態類型的方式指定了他是哪種運算式,確定了要返回的類型和參數,所以他可以跟<Func<int>>一起使用,我們可以用Expression.Lambda來完成這件事,這邊LambdaExpression有提供Compile的方法建立一個委派來執行運算式,延續上面的範例來執行結果.
      • FuncFunc<int> compiled = Expression.Lambda<Func<int>>(add).Compile(); 
        Console.WriteLine(compiled());
        //這邊結果當然就是5
        
      • 接下來我們再把LambdaExpression轉成Expression trees,透過下面的方式來作轉換
      • Expression<Func<int>> return5 = () => 2 + 3; 
        Func<int> compiled = return5.Compile(); 
        Console.WriteLine(compiled());
        
      • 再來看一個較為複雜的例子,我們引用了stirng.StartsWith()的方法來實作
      • Expression<Func<string, string, bool>> expression =
           (x, y) => x.StartsWith(y);
        var compiled = expression.Compile();
        Console.WriteLine(compiled("First", "Second"));
        Console.WriteLine(compiled("First", "Fir"));
        //上面是實際Lambda的做法,我們把他背後做的事情拆開來看
        //宣告幾個運算式必要的條件
        //宣告方法運用到MethodInfo
        MethodInfo method = typeof(string).GetMethod
           ("StartsWith", new[] { typeof(string) });
        //宣告兩個參數
        var target = Expression.Parameter(typeof(string), "x");
        var methodArg = Expression.Parameter(typeof(string), "y");
        //宣告傳入的參數到陣列
        Expression[] methodArgs = new[] { methodArg };
        //建構一個運算式,將前面宣告的方法及參數傳入
        Expression call = Expression.Call(target, method, methodArgs);
        //再來就把它轉換成Lambda運算式
        var lambdaParameters = new[] { target, methodArg };
        var lambda = Expression.Lambda<Func<string, string, bool>>
            (call, lambdaParameters);
        //
        var compiled = lambda.Compile();
        Console.WriteLine(compiled("First", "Second"));
        Console.WriteLine(compiled("First", "Fir"));
        
      • 如果想看Lambda運算式的實際解析的程式碼,可以利用VS的visualizer來查看。
      • 前面講這麼多運算式主要的目的就是LINQ,LINQ就是Lambda Expression + Expression trees + extension methods。
      • LINQ的中心思想就是從一個熟悉的語言生成一個Expression trees,將作為一個中間層,再將轉換成目標平台上的語言,像SQL。
      • Expression trees除了LINQ以外,可以做到3件事,基本上我還不是很瞭書中想表達的這3件事
        1. 優化動態語言運行
        2. 放心的對成員進行重構
        3. 更簡單的反射
    • 改變型別推斷及多載解析
      • 改變的原因:精簡泛型方法的呼叫,這邊利用不指定參數的類型作泛型方法的呼叫,範例如下,
      • static void PrintConvertedValue<TInput,TOutput>
            (TInput input, Converter<TInput,TOutput> converter)
        {
            Console.WriteLine(converter(input));
        }
        //在C#2.0 我們可能就要這樣寫,必須指定傳入、傳出的參數型別
        PrintConvertedValue<string int>("I'm a string", x => x.Length);
        //C# 3.0可直接忽略型別指定,它會自行推斷
        PrintConvertedValue("I'm a string", x => x.Length);
        
      • 匿名方法回傳的類型如有多種則會判斷每個回傳類型是否允許隱含轉換,如下範例會回傳object
      • delegate T MyFunc<T>();
        static void WriteResult<T>(MyFunc<T> function)
        {
            Console.WriteLine(function());
        }
        WriteResult(delegate
        {
            if (DateTime.Now.Hour < 12)
            {
                return 10;
            }
            else
            {
                return new object();
            }
        });
        
      • 兩階段的型別推斷,如有兩個以上的參數型別需要判斷會有兩階段的判斷模式,我們用一個範例來解說
      • static void PrintConvertedValue<TInput,TOutput>
           (TInput input, Converter<TInput,TOutput> converter)
        {
           Console.WriteLine(converter(input));
        }
        PrintConvertedValue("I'm a string", x => x.Length);
        
        1. 第一階段開始
        2. 第一個參數類型(TInput)及參數是string類型,我們推斷string到TInput肯定存在隱含轉換
        3. 第二個參數類型是Convert<TInput, TOutput>類型, 參數是隱含轉換的Lambda運算式,此時不做任何的推斷
        4. 第二階段開始
        5. TInput不依賴任何非固定的參數,所以它被確定為string
        6. 現在第二個參數有一個固定的輸入類型,但有一個非固定的輸出類型,我們可以把它視為(string x) => x.Length,並推斷出返回的類型是int,從int到TOutput會發生一次隱含轉換
        7. 重複第二階段
        8. TOutput不依賴任何非固定參數,所以它被確定為int
        9. 現在沒有非固定的類型參數,推斷成功
      • 選擇正確的多載方法,基本上會去比對最合適的參數型別及參數選擇適當的方法,比較有點讓人模糊的例子我們來看一下
      • static void Execute(Func<int> action)
        {
           Console.WriteLine("action returns an int: " + action());
        }
        static void Execute(Func<double> action)
        {
           Console.WriteLine("action returns a double: " + action());
        }
        Execute(() => 1);
        
        這個例子會選擇第一個方法,因為int to int 跟int to double雖然都合理,但是最合適的還是int to int,這邊有個規則是如果一個匿名函數能轉換成參數列表,但返回類型不同的兩個委派類別,就根據“推斷的返回類型”到“委派的返回類型”的轉換來判定哪個委派轉換更好
  3. 擴充方法(extension method)
    • 在還沒使用擴充方法之前,比如我們要將string或int做處理時,我們會很習慣的寫一個function傳入變數再回傳處理完的值,原則上這也不是錯,只是有點不好看
    • 擴充方法的語法
      • 宣告的方法有幾個要點如下
        1. 必須是靜態類別(static)
        2. 至少要有一個傳入的參數
        3. 第一個傳入的參數必須帶有前綴詞 this
        4. 第一個傳入的參數不得帶有修飾詞(out, ref)
        5. 第一個傳入參數的型別不可以是指標型別
      • 使用擴充方法,來看個範例連同宣告及使用,這是使用到Stream來做複製的動作
      • public static class StreamUtil
        {
           const int BufferSize = 8192;
           public static void CopyTo(this Stream input, Stream output) {
              byte[] buffer = new byte[BufferSize];
              int read;
              while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
              {
                 output.Write(buffer, 0, read);
              }
           }
           public static byte[] ReadFully(this Stream input) {
              using (MemoryStream tempStream = new MemoryStream())
              {
                 CopyTo(input, tempStream);
                 return tempStream.ToArray();
              }
           }
        }
        WebRequest request = WebRequest.Create("http://manning.com"); 
        using (WebResponse response = request.GetResponse())
        using (Stream responseStream = response.GetResponseStream()) 
        using (FileStream output = File.Create("response.dat"))
        {
           responseStream.CopyTo(output);
        }
        
      • 使用擴充方法要小心名稱相同的問題,像上述的範例在.NET 4.0就會失效,因為Stream已有原生的擴充方法叫CopyTo,這種情形下會優先使用Stream.CopyTo,但因為做的事相同,在編譯時也不會有錯誤
      • 如果使用擴充方法的變數是null reference的情況下會怎麼樣呢,之前我們用instance method(執行個體方法)時是不允許用null的,但擴充方法會把他視為一個參數傳入,來看下面範例,第一個回傳的會是false,第二個回傳的會是true,這樣的做法可讀性更佳更清楚
      • using System;
        public static class NullUtil
        {
           public static bool IsNull(this object x)
           {
              return x == null;
           }
        }
        public class Test
        {
           static void Main()
           {
              object y = null;
              Console.WriteLine(y.IsNull());
              y = new object();
              Console.WriteLine(y.IsNull());
           } 
        }
        
    • .NET 3.5的擴充方法,有兩個最常用的類別就是Enumable跟Queryable,這兩個類別擴充了IEnumable跟IQueryable,這邊我們先看Enumable
      • 這邊先用簡單的數字區間來作為範例,使用Range及Reverse兩個方法,這邊會使用到yield的方式來回傳值,有個效能考量在實做一些複雜的功能時要注意的是像範例中我們先用Range取回數字區間再用Reverse做反轉,這時在Reverse裡一定會將所有的值傳回來做反轉的動作再回傳到引用的地方,這時就會使用記憶體空間暫存,當功能複雜資料量大的時候記憶體空間使用量就一定會大,類似Dataset與DataReader,所以在實做時要注意
        var collection = Enumerable.Range(0, 10).Reverse();
        foreach (var element in collection)
        {
           Console.WriteLine(element);
        }
        //得到的結果9,8,7...0
        
      • 使用Where來篩選資料,延續上個範例,再鏈結裡再加一個Where做條件篩選,這邊也有個效能考量,就是在串聯鏈結的時候的順序,如果把Where放在Reverse後面,會變成需要把所有數字都反轉在做篩選,這樣會需要較多的空間及運算,雖然這個小範例沒太大影響,但如果資料量堂大時就要注意了
      • var collection = Enumerable.Range(0, 10).Where(x => x % 2 != 0).Reverse();
        foreach (var element in collection)
        {
           Console.WriteLine(element);
        }
        //得到的結果9,7,5,3,1
        
      • 再來利用Select來取得匿名型別,延續前面的範例,再多加Select來取回原始數字及平方根
      • var collection = Enumerable.Range(0, 10).Where(x => x % 2 != 0).Reverse()
           .Select(x => new { Original = x, SquareRoot = Math.Sqrt(x) } );
        foreach (var element in collection)
        {
           Console.WriteLine("sqrt({0})={1}",
           element.Original,
           element.SquareRoot);
        }
        //得到的結果
        //sqrt(9)=3, sqrt(7)=2.64575131106459, sqrt(5)=2.23606797749979, sqrt(3)=1.73205080756888, sqrt(1)=1
        
      • 接下來看OrderBy、OrderByDescending、ThenBy、ThenByDescending,顧名思義就是排序,OrderBy在前,如有超過一個以上的元素後面叫接ThenBy,繼續之前的範例
      • var collection = Enumerable.Range(-5, 11).Select(x => new { Original = x, Square = x * x })
           .OrderBy(x => x.Square)
           .ThenBy(x => x.Original);
        foreach (var element in collection)
        {
           Console.WriteLine(element);
        }
        //得到的結果
        //{ Original = 0, Square = 0 }, { Original = -1, Square = 1 }, { Original = 1, Square = 1 }, { Original = -2, Square = 4 }
        //{ Original = 2, Square = 4 }, { Original = -3, Square = 9 }, { Original = 3, Square = 9 }, { Original = -4, Square = 16 }
        //{ Original = 4, Square = 16 }, { Original = -5, Square = 25 }, { Original = 5, Square = 25 }
        
      • 至於運用到商業邏輯的部分就實作一些功能實再來看其他相關的功能特性,像Sum、GroupBy等
    •  使用的思路和原則
      • 作者提到extending the world(擴展世界)、fluent interfaces(流暢的介面)、理智的使用擴充方法,主要是提到擴充方法的好處,但也不是所有情況都適用,必須要視情形、環境、團隊來決定是否使用。
  4. 查詢運算式和LINQ to Object
    • 介紹一下LINQ是甚麼
      • LINQ最重要的一個部分就是序列(sequence),這邊利用一個簡單的例子來看一下,adultNames最後會得到一個IEnumerable<string>裡面包含人的年紀大於18歲的人,他的查詢運算式的處理順序在第一行會先取得people裡的所有物件,第二行再依據第一行的結果做篩選年紀大於18的有那些,最後一行則依據前面篩選完的物件取回人的名字
      • var adultNames = from person in people
                         where person.Age >= 18
                         select person.Name;
        
      • LINQ還有另一個特色是延遲處理,前面範例宣告時只是在記憶體裡儲存運算式,直到讀取第一筆資料時才真正執行運算式,他實際執行順序,讀取第一筆時,他會先提取select的部分需要哪些,再來提取where條件,接著就是people集合,這時他會先丟一筆資料給where看條件是否符合,符合救回給select,不符合則繼續取下一筆,直到取到完,不管它的筆數有多少,一次只會傳回一筆
    • 這邊說明一下LINQ的語法在編譯器會轉化成查詢運算式,像以下的範例
    • //一段LINQ的查詢語法
      var query = from user in SampleData.AllUsers
                  select user;
      //編譯器轉換成
      var query = SampleData.AllUsers.Select(user => user);
      
    • 過濾及排序
      • 運用where方法來做條件過濾,一樣他在編譯時期會轉換成查詢運算式
      • User tim = SampleData.Users.TesterTim;
        var query = from defect in SampleData.AllDefects
                    where defect.Status != Status.Closed 
                    where defect.AssignedTo == tim 
                    select defect.Summary;
        //編譯器轉換成
        SampleData.AllDefects.Where(defect => defect.Status != Status.Closed)
                             .Where(defect => defect.AssignedTo == tim)
                             .Select(defect => defect.Summary)
        
      • 運用orderby方法來做條件排序,延續前面的範例
      • var query = from defect in SampleData.AllDefects
                    where defect.Status != Status.Closed
                    where defect.AssignedTo == tim
                    orderby defect.Severity descending, 
                            defect.LastModified 
                    select defect;
        //編譯器轉換成
        SampleData.AllDefects.Where(defect => defect.Status != Status.Closed)
                             .Where(defect => defect.AssignedTo == tim)
                             .OrderByDescending(defect => defect.Severity)
                             .ThenBy(defect => defect.LastModified)
        
        
      • 這邊可能會看到一個奇怪的現象,就是前面orderby、thenby後的select不見了,作者說這是退化查詢運算式(Degenerate query expressions),其實就是在select出來的內容如果沒有特俵要選哪幾個元素時他會省略掉這個步驟,因為多此一舉,得到的結果還是一樣
    • let子句及透明標示符(transparent identifiers)
      • 用let來做中間的計算,let就是引入另一個運算式指定到變數中,看下面範例,length另外去存取名稱的長度,直接在運算式中做引用
      • var query = from user in SampleData.AllUsers
                    let length = user.Name.Length
                    orderby length
                    select new { Name = user.Name, Length = length };
        
      • 透明標示符(transparent identifiers),上面使用let的方法就是透明標示符,作者的解釋是let另外調用了一個方法來讓select做使用,因為最終要查詢出指定的項目髓以會變成兩層的select,編譯器會自動模擬出第二層,而範例中的z就是編譯器隨機產生出來的,前面範例經由編譯器轉換會變成如下所示
      • SampleData.AllUsers
                  .Select(user => new { user, length = user.Name.Length })
                  .OrderBy(z => z.length)
                  .Select(z => new { Name = z.user.Name, Length = z.length })
        
    • 聯接,join子句
      • join的寫法我們直接看範例,運用到join、on、equal
      • var query = from defect in SampleData.AllDefects
                    join subscription in SampleData.AllSubscriptions
                    on defect.Project equals subscription.Project
                    select new { defect.Summary, subscription.EmailAddress };
        
      • 如果有需要過濾條件,可以評估是否可在聯接前先做過濾,增加效能,如下範例有兩種方式,第一種方式較直覺
      • //方法1
        from defect in SampleData.AllDefects
        where defect.Status == Status.Closed
        join subscription in SampleData.AllSubscriptions
           on defect.Project equals subscription.Project
        select new { defect.Summary, subscription.EmailAddress }
        //方法2
        from subscription in SampleData.AllSubscriptions
        join defect in (from defect in SampleData.AllDefects
                        where defect.Status == Status.Closed
                        select defect)
           on subscription.Project equals defect.Project
        select new { defect.Summary, subscription.EmailAddress }
        
      • 使用join into的方式做分組聯接,可將一個聯接的整個集合寫到一個變數中,如下範例
      • var query = from defect in SampleData.AllDefects
                    join subscription in SampleData.AllSubscriptions
                       on defect.Project equals subscription.Project
                       into groupedSubscriptions
                    select new { Defect = defect,
                                 Subscriptions = groupedSubscriptions };
        
      • 再來看一個模擬sql left join 的做法,如以下範例
      • var dates = new DateTimeRange(SampleData.Start, SampleData.End);
        var query = from date in dates
                    join defect in SampleData.AllDefects
                       on date equals defect.Created.Date
                       into joined
                    select new { Date = date, Count = joined.Count() };
        
      • 多個from子句做交叉聯接和合併,可以把兩個完全不相關的集合合併成一個查詢結果,如下範例
      • var query = from user in SampleData.AllUsers
                    from project in SampleData.AllProjects
                    select new { User = user, Project = project };
        
      • 另外來看交叉的部分可以怎麼做,如下範例,可依據第一行的查詢結果帶入第二行的查詢當作參數
      • var query = from left in Enumerable.Range(1, 4)
                    from right in Enumerable.Range(11, left)
                    select new { Left = left, Right = right };
        //這段依據編譯器轉換會運用到SelectMany的擴充方法
        Enumerable.Range(1, 4)
                  .SelectMany(left => Enumerable.Range(11, left),
                             (left, right) => new {Left = left, Right = right})
        
        //
        //另一個比較實用的範例可用來讀取檔案
        var query = from file in Directory.GetFiles(logDirectory, "*.log") 
                    from line in ReadLines(file)
                    let entry = new LogEntry(line)
                    where entry.Type == EntryType.Error
                    select entry;
        
    • 分組和延續(Groupings and continuations)
      • group by子句來做分組資料,看以下範例
      • var query = from defect in SampleData.AllDefects
                              where defect.AssignedTo != null
                              group defect by defect.AssignedTo;
        //讀取的時候要用key取出分組的資料
        foreach (var entry in query)
        {
            Console.WriteLine(entry.Key.Name);
            foreach (var defect in entry)
            {
                Console.WriteLine(" ({0}) {1}",
                defect.Severity, defect.Summary);
            }
            Console.WriteLine();
        }
        
      • 延續的意思就是第一段查詢出來的結果放到第二段繼續處理,這邊也用了into來把結果暫存到變數中,如以下範例,同時把名字及數量讀出來
      • var query = from defect in SampleData.AllDefects
                    where defect.AssignedTo != null
                    group defect by defect.AssignedTo into grouped 
                    select new { Assignee = grouped.Key,
                                 Count = grouped.Count() };
        
      • 再來看看超過兩個延續的做法,延續前面的範例再加上排序在讀出結果,如下範例
      • var query = from defect in SampleData.AllDefects
                    where defect.AssignedTo != null
                    group defect by defect.AssignedTo into grouped
                    select new { Assignee = grouped.Key,
                                 Count = grouped.Count() } into result 
                    orderby result.Count descending
                    select result;
        
    • 這邊要思考的一件事就是查詢運算式及點標記(擴充方法的方式)的優缺,其實兩種方式都好沒有哪種方法比較好或對錯,取決於個人的習慣或團隊的習慣,基本上以程式碼可讀性高為原則。
  5.  超越集合的LINQ(LINQ beyond collections)
    • LINQ to SQL,這邊介紹了簡單的資料模型做範例,主要就是依據上一章節所描述的LINQ最終轉化成SQL的語法,這邊範例程式碼就不多做解釋了
    • 用IQueryable與IQueryProvider進行轉換
      • IQueryable<T>和介面的介紹
        1. IQueryable<T>繼承了IEnumable<T>、IEnumable、IQueryable
        2. IQueryable只有3個屬性,QueryProvider(IQueryProvider類別)、ElementType、Expression
      • 模擬介面來實現紀錄調用
      • 把運算式黏在一起,Queryable的擴充方法,這邊提到幾件事還蠻重要的
        1. LINQ to SQL能夠完成工作的主要原因有4個,都是C#3.0的特性,Lambda運算式、查詢運算式到使用Lambda運算式的普通運算式轉換、擴充方法及運算式樹。
        2. Enumable使用委派當作參數,Queryable使用運算式樹當作參數
        3. 這邊提到了Enumable與Queryable的差異性,Enumable原則上會直接去執行比如說條件篩選等,Queryable他實際上是產生運算式樹,直到執行excute時才真正的運作。 
    • LINQ to XML,這邊提到了一些跟XML文檔有關的寫入及讀取,用LINQ來操作XML相當方便
      • LINQ to XML中的核心類型都在System.Xml.Linq,主要的類型如下
        1. XName-表示元素和特性的名稱
        2. XNamespace-表示XML的命名空間
        3. XObject-XNode、XAttribute的父類別
        4. XNode-XML的節點
        5. XAttribute-包含名/值的特性
        6. XContainer-XML中可以包含子內容的節點
        7. XText-文本節點
        8. XElement-元素
        9. XDocument-文檔
      •  這邊來看一下簡單的例子瞭解他是如何實做的
      • var users = new XElement("users",
            SampleData.AllUsers.Select(user => new XElement("user",
               new XAttribute("name", user.Name),
               new XAttribute("type", user.UserType)))
        );
        Console.WriteLine(users);
        // Output
        <users>
          <user name="Tim Trotter" type="Tester" />
          <user name="Tara Tutu" type="Tester" />
          <user name="Deborah Denton" type="Developer" />
          <user name="Darren Dahlia" type="Developer" />
          <user name="Mary Malcop" type="Manager" />
          <user name="Colin Carton" type="Customer" />
        </users>
        
      • 查詢單個節點,查詢節點有幾個類別方法可用,這些方法的功能都跟字面上的意思一樣
        1. Ancestors
        2. AncestorsAndSelf
        3. Annotations
        4. Atributes
        5. Descendants
        6. DescendantAndSelf
        7. DescendantNodes
        8. DescendantNodesAndSelf
        9. Elements
        10. ElementAfterSelf
        11. ElementsBeforeSelf
        12. Nodes 
    • 平行LINQ,平行LINQ是LINQ to Object的平行(parallel)實做,簡稱PLINQ,一般我們寫的LINQ都是使用並行(concurrency)執行,也就是單執行緒,遇到需要大量運算的時候效能會比較低落,所以運用到平行運作,也就是多執行緒來執行,核心數越多效果就越高
      • 需要運用到這種技巧來寫的通常是有兩個from做交叉查詢時,這邊我們簡單看個小範例
      • var DictTwo = from i in Enumerable.Range(0, 999999).AsParallel().AsOrdered()
                      where isPrime(i)
                      select i;
        private static bool isPrime(int number)
        {
            if (number < 2) 
            {
                return false;
            }
            int limite = (int)Math.Sqrt(number);
            for (int i = 2; i <= limite; i++)
            {
                if (number % i == 0) 
                {
                    return false;
                }
            }
            return true;
        }
        
      • 另外還有一些可改變查詢行為的方法如下
        1. AsUnordered-讓有序查詢便無序
        2. WithCancellation-取消標記
        3. WithDegreeOfParallelism-指定執行查詢的最大開發任務數
        4. WithExecutionMode-強制查詢按並行方式執行
        5. WithMergeOptions-可以改變對結果的緩衝方式
    • LINQ to Rx,這是非同步的作法,這如以後有需要用到再回來看,本書這部分提到的也不多,他的全名是Reactive Extensions,如要使用要再另外下載SDK,還有大大寫的教學文可供參考。
    • 擴充LINQ to Objects的方法,這邊主要是依據自己的需求去擴充方法,有幾點設計及實作應該注意的問題
      • 單元測試-避免有空序列或無效參數
      • 檢查參數-在調用方法的同時執行參數檢查
      • 優化-使用ICollection
      • 文檔-在文檔中指名程式碼對輸入的處理和運算式的預期性能很重要
      • 盡量迭代一次-盡量不要跑兩次
      • 釋放迭代器-可以對迭代器用using,平常都是使用foreach自動幫我們釋放了
      • 自定義比較器-自行覆寫比較的IEquality<T>和IComparer<T>
      • 這邊看個範例對照前面的注意事項,除了沒有自定義比較器外都有符合
      • public static T RandomElement<T>(this IEnumerable<T> source, Random random)
        {
           if (source == null)
           {
              throw new ArgumentNullException("source");
           }
           if (random == null)
           {
              throw new ArgumentNullException("random");
           }
           ICollection collection = source as ICollection;
           if (collection != null)
           {
              int count = collection.Count;
              if (count == 0)
              {
                  throw new InvalidOperationException("Sequence was empty.");
              }
              int index = random.Next(count);
              return source.ElementAt(index);
           }
        
           using (IEnumerator iterator = source.GetEnumerator())
           {
              if (!iterator.MoveNext())
              {
                 throw new InvalidOperationException("Sequence was empty.");
              }
              int countSoFar = 1;
              T current = iterator.Current;
              while (iterator.MoveNext())
              {
                 countSoFar++;
                 if (random.Next(countSoFar) == 0)
                 {
                    current = iterator.Current;
                 }
              }
              return current;
           }
        }
        

2016年7月17日 星期日

DOM深入精要

  1. 節點概論
    • 常見的節點物件類型,他個別有常數值來表示,可以使用屬性的nodeType來取得
      • DOCUMENT_NODE = 9
      • ELEMENT_NODE = 1
      • ATTRIBUTE_NODE = 2
      • TEXT_NODE = 3
      • DOCUMENT_FRAGMENT_NODE = 11
      • DOCUMENT_TYPE_NODE = 10
    • 處理節點的屬性
      • childNodes-取得子節點的集合
      • firstChild-取得第一個節點,但他會包含文字及註解
      • lastChild-取得最後一個節點,但他會包含文字及註解
      • nextSibling-取得下一個節點,但他會包含文字及註解
      • nodeName-取得節點的名稱
      • nodeType-取得節點的類型
      • nodeValue-取得節點的值
      • parentNode
      • previousSibling-取得上一個節點,但他會包含文字及註解
    • 處理節點的方法
      • appendChild()-加入節點,加在節點的最後一個
      • cloneNode()-複製節點
      • //只複製節點本身
        document.querySelector('ul').cloneNode();
        //複製節點及子結點
        document.querySelector('ul').cloneNode(true);
        
      • compareDocumentPosition()-取得所選取的節點及傳入的節點之間的資訊
      • contains()-是否包含某個節點
      • hasChildNodes()-是否有子節點
      • insertBefore()-加入節點,加在節點的前一個節點
      • //在ul的第一個li前加入一個新的li
        ul.insertBefore(li, ul.firstChild);
        
      • isEqualNode()-判斷節點是否相同
      • removeChild()-移除節點
      • replaceChild()-替換節點
      • //把divA替換成newSpan
        divA.parentNode.replaceChile(newSpan, divA);
        
    • 文件方法
      • document.createElement()-建立元素
      • document.createTextNode()-建立文字節點
    • HTML Element 屬性
      • innerHTML-建立HTML字串,並加入到DOM
      • outerHTML-建立HTML字串,並取代自己
      • textContent-建立文字節點,並加入到DOM
      • innerText-建立文字節點,並加入到DOM(非標準的用法)
      • outerText-建立文字節點,並取代自己(非標準的用法)
      • firstElementChild-取得第一個節點,但他不包含文字及註解
      • lastElementChild-取得最後一個節點,但他不包含文字及註解
      • nextElementChild-取得下一個節點,但他不包含文字及註解
      • previosElementChild-取得上一個節點,但他不包含文字及註解
      • children
    • HTML 元素方法
      • insertAdjacentHTML()-這個方法比前面inner或outer還要精確,來看個範例
      • <i id="elm">how</i>
        var elm = document.getElementById("elm");
        elm.insertAdjacentHTML('beforebegin', '<span>Hey-</span>');
        elm.insertAdjacentHTML('afterbegin', '<span>dude-</span>');
        elm.insertAdjacentHTML('beforeend', '<span>-are</span>');
        elm.insertAdjacentHTML('afterend', '<span>-you?</span>');
        //
        console.log(document.body.innerHTML);
        //<span>Hey-</span><i id="A"><span>dude-</span>how<span>-are</span></i>
        //<span>-you?</span>
        
    • 節點通常取回來的型態是NodeList或HTMLCollection,他很像陣列但又不是javascript的陣列,如果要轉成javascript的陣列可以透過js的方法來轉型
    • [].slice.call(ulElementChildNodes);
      
  2. 文件節點
    • HTMLDocument常用的屬性與方法
      • docType-文件定義類型
      • documentElement-html Tag的節點
      • implementation.*-document.implementation.hasFeature()方法來偵測規格,像是Core、CSS等支援的版本,但又不能全然的信任,還是要自行偵測
      • activeElement-取得目前focus的節點
      • body-取得身體的部分
      • head-取得頭的部分
      • title-取得標題名稱
      • lastModified-取得最後一次修改的時間
      • referrer-取得網址參考
      • URL-取得完整網址
      • defaultView-取得window物件
      • compatMode-取得相容的版本
      • ownerDocument-取得自身的windows物件
      • hasFocus()-判斷節點是否有聚焦
  3. 元素節點
    • HTML * Element物件的屬性與方法
      • createElement()-建立元素
      • tagName-取得tag名稱
      • children-取得子元素
      • getAttribute()-取得屬性
      • setAttribute()-設定屬性
      • hasAttribute()-判斷是否有此屬性
      • removeAttribute()-移除屬性
      • classList()-class的集合,可運用add()、remove()、contains()、toggle()來設定class
      • dataset-屬性裡為data-開頭的屬性
      • attributes-取得屬性集合
  4. 選擇元素節點
    • 有兩種取元素的種類方式
      • 一種是平常常用的getElementById、getElementByTagName、getElementByClassName等
      • 另一種則是querySelector()、querySelectorAll()
      • 第一種可隨著頁面的動態取得當下的元素,但第二種方式則是取得頁面一開始完成節點的快照 
  5. 元素節點幾何與捲動幾何
    • 這章講到幾個跟元素大小距離有關的用法
      • offsetParent-父層
      • offestTop-取得離上麵的距離
      • offsetLeft-取得離右邊的距離
      • getBoundingClientRect()-可以取得上下左右的邊寬大小
      • offsetHeight-取得整體的高度(邊線+內拒+內容)
      • offsetWidth-取得整體的寬度(邊線+內拒+內容)
      • clientHeight-取得整體的高度(內拒+內容)
      • clientWidth-取得整體的寬度(內拒+內容)
      • elementFromPoint()-取得特定地癲最上面的元素
      • scrollHeight-捲動的節點的高度
      • scrollWidth-捲動的節點的寬度
      • scrollTop-取得設定距離上方捲動的像素
      • scrollLeft-取得設定距離左方捲動的像素 
      • scrollIntoView()-將元素捲入可視區
  6.  元素節點行內樣式
    • 這章節基本上就是在說元素的樣式設定在tag的style裡,原則上跟CSS差不多,而且現在大部分都以設定CSS為主要方式,所以這章節跳過
  7. 文字節點
    • 幾個常用的屬性
      • textContent-設定會取回所有文字節點
      • splitText()-將文字節點拆開來
      • appendData()-加入文字到文字節點的最後面
      • deleteData()-移除文字節點裡的某一段文字,用字元的起始位置及長度
      • insertData()-插入文字節點裡的某一段文字,用字元的起始位置及文字
      • replaceData()-替換文字節點裡的某一段文字,用字元的起始位置、長度及文字
      • subStringData()-取得文字節點裡的某一段文字,用字元的起始位置及長度
      • normalize()-將兩個同級的文字節點合併在一起
      • data-與nodeValue一樣會取回文字節點的內容
      • document.createTextNode()-建立一個純文字的節點
  8. DocumentFragment節點
    • 為了避免動態網頁有過多的渲染,我們盡可能使用DocumentFragment來做一次性的插入HTML文件,操作方式都相同,只是要先把create出來的節點及元素先append到Fragment在append到document
  9. CSS樣式表與CSS規則
    • 這章節都在說明可對CSS檔案做一些增刪,基本上我們不太會這樣使用
  10. DOM的JavaScript
    • 用defer 來延遲下載及執行js檔
    • 用async來非同步下載及執行js檔
  11. DOM事件
    • 介紹了所有事件,像是onclick、onfocus、onblur等
    • 事件得冒泡行為
    • addEventListener()-註冊事件
    • removeEventListener()-移除註冊的事件
    • preventDefault()-取消預設的事件
    • stopPropagation()-停止事件流,也就是冒泡行為
    • stopImmediatePropagation()-停止事件流及同一個目標的其他事件
    • createEvent()-建立事件
    • initCustomEvent()-初始化事件
    • dispatchEvent()-呼叫自訂事件

2016年6月23日 星期四

wkHTMLToPDF及TuesPechkin的使用

因專案需求需要運用到HTML轉PDF,之前有使用過iTextSharp轉過PDF檔,但這次HTML有包含圖片檔導致轉出來有些不正常,後來找到[C#] 網頁Html轉PDF檔(一行程式碼解決) ,發現有這麼方便的工具,而且轉出來也很正常,這是在主機上安裝一個EXE的執行程式,可以到wkhtmltopdf官網下載並安裝,要注意的是除了有分32/64還有分作業系統,Windows就有分2003/XP及7/vista,看起來是編譯的方式不一樣,程式代碼如下
public ActionResult Print()
{
    Process process = new Process();
    string serverPath = Server.MapPath("~");
    string filePath = serverPath + @"\Temp\" + "test.pdf";
    //
    process.StartInfo.UseShellExecute = true;
    //wkhtmltopdf.exe應用程式放的位置
    process.StartInfo.FileName = serverPath + @"\wkhtmltopdf.exe";
    //參數為網址 + " " + 實體檔案位置
    process.StartInfo.Arguments = "http://www.google.com.tw" + " " + filePath;
    process.Start();
    //等待執行完成
    process.WaitForExit();
    //
    return File(filePath, "application/pdf", "工作報告單");
}
後來因為一些問題有看到黑暗大的一篇文章HTML轉PDF - 使用Pechkin套件,發現有人把wkhtmltopdf轉成DLL可用的套件,但同時又看到[ASP.net MVC] 在Web專案上使用Pechkin套件將網頁轉成PDF檔裡面說到此套件的DLL有BUG,原作者有另外更新此DLL。
但我在NuGet上找這個套件時又看到TuesPeckin的套件,查看了一下他骨子裡也是同一套,就想說用看看,果然產出的一模一樣。 相關做法如下:
  1. 先到NuGet上下安裝TuesPechkin,另外看是在32/64位元環境,分別安裝TuesPechkin.Wkhtmltox.Win32/TuesPechkin.Wkhtmltox.Win64
  2. 引用程式代碼
  3. using TuesPechkin;
    
    public class HtmlToPDF
    {
        public IConverter Converter { get; private set; }
        public IConverter anotherConverter { get; private set; }
        public string Url { get; private set; }
    
        public HtmlToPDF(string url)
        {
            Url = url;
        }
    
        public byte[] GetPDF()
        {
            var doc = new HtmlToPdfDocument();
            var objSettings = new ObjectSettings()
            {
                PageUrl = Url
            };
            doc.Objects.Add(objSettings);
            Converter =
                new ThreadSafeConverter(
                    new RemotingToolset(
                        new Win32EmbeddedDeployment(
                            new TempFolderDeployment())));
            var result = Converter.Convert(doc);
            //
            return result;
        }
    }
    
    public ActionResult Print()
    {
        HtmlToPDF htmlToPdf = new HtmlToPDF("http://www.google.com.tw");
        byte[] pdfFile = htmlToPdf.GetPDF();
        //
        return File(pdfFile, "application/pdf", "test.pdf");
    }
    
參考網址:
[C#] 網頁Html轉PDF檔(一行程式碼解決)
wkhtmltopdf官網
HTML轉PDF - 使用Pechkin套件
[ASP.net MVC] 在Web專案上使用Pechkin套件將網頁轉成PDF檔
TuesPeckin

2016年6月12日 星期日

深入理解C#(C# in Depth) 讀書心得 Part 1

Part 1 基礎知識(Preparing for the journey)
  1. C#開發的進化史(The changing face of C# development)
    •  C#從1.0到4.0,透過一個簡單的例子說明各版本的寫法優劣差異,及各版本的一些特性。
  2. C#1所搭建的核心基礎(Core foundations:building on C# 1)
    • 委派(Deletates)
      •  委派的4個條件
        1. 宣告委派類型
        2.     //宣告委派類型
              delegate void StringProcessor(string input);
          
        3. 必需有一個執行代碼的方法,方法的參數及回傳的類型皆要一致
        4.     public class Person
              {
                  string name;
                  public Person(string name)
                  {
                      this.name = name;
                  }
          
                  public void Say(string message)
                  {
                      Console.WriteLine("{0} says: {1}", name, message);
                  }
              }
          
        5. 必須建立一個委派實例化,可以是靜態方法或參考方法
        6.     Person jon = new Person("Jon");
              Person tom = new Person("Tom");
              StringProcessor jonsVoice, background;
              jonsVoice = new StringProcessor(jon.Say);
          
        7. 必須調用(invoking)委派方法
        8.     jonsVoice += tom.Say;
              jonsVoice.Invoke(Hello, son.);
              //OR
              jonsVoice("Hello, son.");
          
      • 委派可以增加或刪除一個委派實例
      • 事件不是委派實例,只是成對的add/remove方法
    • 系統類型的特色(Type system characteristics) 
      •  C#1~3是靜態類型系統,他是明確的、安全的
      •  靜態與動態類型的差異性在於靜態類型在撰寫上有定義明確的型別,動態類型在撰寫上沒有明確定義型別
      • //靜態類型
        string s = "hello";
        //動態類型
        var s = "hello";
        
      • 動態類型較為不安全,因動態類型必須在執行階段才可知道型別是否正確會不會造成錯誤
      • 靜態類型不允許一個集合成為強型別的string或int,必須要透過強制轉型,這樣就無法達到編譯安全性
      • string[] strings = new string[5];
        object[] objects = strings;
        //這邊會報出ArrayTypeMismatchException
        objects[0] = new Button();
        
      • 可變性分為兩種,共變數(covariance)、反變數(contravariance),共變數就是可將衍生型別轉型成基礎型別(object),反變數則相反,可將基礎型別轉型成衍生型別
      • 方法覆蓋及介面實作皆不支援共變數及反變數
    • 值/參考型別(Value/reference types)
      • 值與參考型別使用分類不能單就效能來取決,要看用在什麼地方,依據情境來決定該使用哪種
      • 參考型別一定在記憶體的堆積(heap)上,但值型別並不一定在記憶體的堆疊(stack)上
      • 物件在C#中預設不是透過參考傳遞(pass by reference)的,這關係到左值(l-value)
      • boxing和unboxing使用上要注意效能問題,盡可能少用
    • 基於前三種基礎的新特性
      • 委派的新特性
        • C# 2.0提供了泛型委派、委派的表達式、匿名的寫法(不直覺)、委派的共變數及反變數
        • //C# 2.0依前面的例子,可用此種方式指派(隱含轉換)
          jonsVoice = jon.Say;
          
        • C# 3.0提供了Lambda的寫法
      • 系統類型的新特性
        • C# 2.0提供泛型
        • C# 3.0提供匿名型別、隱含型別、擴充方法
        • C# 4.0提供動態型別、有限制的泛型共變數及反變數
      • 值/參考型別的新特性
        • C# 2.0提供泛型、可以為null
Part 2 C# 2 解決C# 1 的問題(C# 2 : Solving the issues of C# 1)
  1. 用泛型實現參數化類型(Parameterized typing with generics)
    • 泛型帶來了更好的編譯時檢查、更清楚的看到所要表達的訊息,當然還有IDE的支持及型能的提升,減少boxing和unboxing
    • 泛型有泛型類型(generic types)跟泛型方法(generic methods)
    • 最簡單的泛型就是Dictionary<TKey, TValue>
    • 泛型類型分為兩種,未繫結泛型類型(unbound generic type)就是泛型角框號裡的型別是T的,已建構類型(constructed type)
    • //未繫結泛型類型
      Dictionary<Tkey, Tvalue> unbound = New Dictionary<Tkey, Tvalue>();
      //已建構類型
      Dictionary<string, int> cons = New Dictionary<string, int>();
      
    • 已建構類型有分開放類型(open type)、封閉類型(closed type),未繫結泛型類型基本上就是開放類型,其他皆為封閉類型
    • 泛型方法
    • static double TakeSquareRoot(int x)
      {
          return Math.Sqrt(x);
      }
      List<int> integers = new List<int>();
      integers.Add(1);
      integers.Add(2);
      integers.Add(3);
      integers.Add(4);
      Converter<int double> converter = TakeSquareRoot;
      List<double> doubles;
      //調用泛型方法
      doubles = integers.ConvertAll(converter);
      foreach (double item in doubles)
      { }
      
    • 泛型類型的條件約束(type constraint)
    • //參考型別限制
      struct RefSample<T> where T : class { }
      //值型別限制
      class ValSample<T> where T : struct { }
      //建構式限制
      public T CreateInstance<T>() where T : new()
      {
          return new T();
      } 
      //轉換型別限制
      class Sample<T> where T : Stream
      struct Sample<T> where T : IDisposable
      class Sample<T> where T : IComparable<T>
      //T繼承至U
      class Sample<T, U> where T : U
      //組合限制
      class Sample<T> where T : class, IDisposable, new()
      class Sample<T, U> where T : class where U : struct, T
      
    • 泛型類型的推斷(type inference)
      • 泛型類型推斷只適用於泛型方法
      • static Lits<T, U> MakeList<T, U>(T first, Tsecond);
        List<string, U> list = MakeList<string, U>("Line 1", "Line 2");
        //類型推斷可簡化寫法成
        List<string, U> list = MakeList("Line 1", "Line 2");
        
    • 實作泛型(Implementing generics),自行撰寫泛型時有幾件事需要注意
      1. 預設關鍵字(Default value Expressions),利用default取出型別的預設值
      2. static int CompareToDefault<T, U>(T value)
            where T : IComparable<T, U>
        {
            return value.CompareTo(default(T));
        }
        Console.WriteLine(CompareToDefault("x")); //1
        Console.WriteLine(CompareToDefault(10)); //1
        Console.WriteLine(CompareToDefault(0)); //0
        Console.WriteLine(CompareToDefault(-10)); //-1
        Console.WriteLine(CompareToDefault(DateTime.MinValue)); //0
        
      3. 直接比較(Direct comparisons),運用到多載運算子(overload operator),泛型T不可直接==或!=比較,必須要運用EqualityComparer<T>、Comparer<T>
      4. static bool AreReferencesEqual<T>(T first, T second)
            where T : class
        {
            return first == second;
        }
        string name = "Jon";
        string intro1 = "My name is " + name;
        string intro2 = "My name is " + name;
        Console.WriteLine(intro1 == intro2);  //true
        Console.WriteLine(AreReferencesEqual(intro1, intro2)); //false
        
      5. 一個完整的泛型實作範例
      6. public sealed class Pair<T1, T2> : IEquatable<Pair<T1, T2>>
            {
                private static readonly IEqualityComparer<T1> FirstComparer =
                    EqualityComparer<T1>.Default;
                private static readonly IEqualityComparer<T2> SecondComparer =
                    EqualityComparer<T2>.Default;
                private readonly T1 first;
                private readonly T2 second;
        
                public Pair(T1 first, T2 second)
                {
                    this.first = first;
                    this.second = second;
                }
        
                public T1 First { get { return first; } }
                public T2 Second { get { return second; } }
        
                public bool Equals(Pair<T1, T2> other)
                {
                    return other != null &&
                        FirstComparer.Equals(this.First, other.First) &&
                        SecondComparer.Equals(this.Second, other.Second);
                }
        
                public override bool Equals(object o)
                {
                    return Equals(o as Pair<T1, T2>);
                }
        
                public override int GetHashCode()
                {
                    return FirstComparer.GetHashCode(first) * 37 +
                    SecondComparer.GetHashCode(second);
                }
            }
        
    • 高級泛型(Advanced generics)
      • 回頭再看一次
    • 泛型在C#的限制(Limitations of generics in C#)
      • 缺乏可變性,這部分我倒認為蠻好的,這確保了型別的安全性,在C#4.0前有一些方式可處理共變數及反變數,但處理的方式我覺得都不太直覺
      • 缺乏運算子操作約束,也就是在泛型類別裡,運用到 += 這種運算子去加T型別時造成的問題,C#4.0前沒有好的方法可解決
      • 缺乏泛型屬性、索引器和其他成員類型,這個問題一般來說不太會遇到
  2. Null的類型 (Saying nothinh with nullable types)
    • 在C# 1.0 時還沒有nullable的型別,有三種模式可以來表示nullable 
      • 魔術值(The magic value),基本上就是定義一個空值讓耀表示空值得來指定,像Datetime可以把空值指定到Datetime.MinValue,ADO.NET就會使用DBNULL.Value來表示
      • 包裝參考型別
      • 額外的註記
    •  System.Nullable<T> and System.Nullable
      • System.Nullable<T>是Value Type
      • 有HasValue及Value,HasValue提供Boolen
      • GetValueOrDefault如果沒有值則基於基底型別提供預設值,有則同Value,或者可以給予預設值,ex : GetValueOrDefault(10)
    • C# 2 的語法糖
      • 使用?來寫跟System.Nullable<T>一樣,ex: int?
      • 提到Nullable與非Nullable的比較及運算式,這比較沒什麼爭議,基本上很直覺的可以判斷
      •  可以使用as語法來判斷是否可轉型,確保型別判斷正確,但這部分有效能考量,這牽扯到boxing和unboxing,使用上得注意
      • static void PrintValueAsInt32(object o)
        {
            int? nullable = o as int?;
            Console.WriteLine(nullable.HasValue ?
                              nullable.Value.ToString() : "null");
        }
        PrintValueAsInt32(5); //5
        PrintValueAsInt32("some string"); //null
        
      • 使用 ?? 的語法(null coalescing operator)來簡化程式碼並提高可讀性,原則上第一個值的判斷必須要是可為Null的型別,不然就毫無意義了
      • //原本的寫法
        DateTime lastAlive = (death == null ? DateTime.Now : death.Value);
        return lastAlive - birth;
        //??的方式
        DateTime lastAlive = death ?? DateTime.Now;
        return lastAlive - birth;
        //也可簡化成1行
        return (death ?? DateTime.Now) - birth;
        //判斷的變數的數量沒有限制,可寫成這樣
        Address contact = user.ContactAddress ??
                          order.ShippingAddress ??
                          user.BillingAddress;
        
    • Nullable的額外特殊用法,利用??的語法延伸出的變化,下面範例是可以簡化類別比較的寫法
    • public static class PartialComparer
      {
         public static int? Compare<T>(T first, T second)
         {
            return Compare(Comparer<T>.Default, first, second);
         }
         public static int? Compare<T>(IComparer<T> comparer,
                                       T first, T second)
         {
            int ret = comparer.Compare(first, second);
            return ret == 0 ? new int?() : ret;
         }
         public static int? ReferenceCompare<T>(T first, T second)
            where T : class
         {
             return first == second ? 0
                  : first == null ? -1
                  : second == null ? 1
                  : new int?();
         } 
      }
      public int Compare(Product first, Product second)
      {
          return PC.ReferenceCompare(first, second) ??
                 PC.Compare(second.Popularity, first.Popularity) ??
                 PC.Compare(first.Price, second.Price) ??
                 PC.Compare(first.Name, second.Name) ??
                 0;
      }
      
  3. 進入快速通道的委派(Fast-tracked delegates)
    • 跟笨拙的委派語法說拜拜(Saying goodbye to awkward delegate syntax),以下範例是C# 1.0
    • static void LogPlainEvent(object sender, EventArgs e)
      {
         Console.WriteLine("LogPlain");
      }
      static void LogKeyEvent(object sender, KeyPressEventArgs e)
      {
         Console.WriteLine("LogKey");
      }
      static void LogMouseEvent(object sender, MouseEventArgs e)
      {
         Console.WriteLine("LogMouse");
      }
      ...
      Button button = new Button();
      button.Text = "Click me";
      button.Click += new EventHandler(LogPlainEvent); 
      button.KeyPress += new KeyPressEventHandler(LogKeyEvent); 
      button.MouseClick += new MouseEventHandler(LogMouseEvent);
      Form form = new Form();
      form.AutoSize = true;
      form.Controls.Add(button);
      Application.Run(form);
      
    • 群組方法轉換(Method group conversions),可簡略語法,但還不算是很好的寫法
    • 共變數與反變數(Covariance and contravariance),下面範例是前面看到的範例改以C# 2.0的方式,看起來沒什麼意義只是要表達這樣的寫法
    • static void LogPlainEvent(object sender, EventArgs e)
      {
         Console.WriteLine("An event occurred");
      }
      Button button = new Button();
      button.Text = "Click me";
      button.Click += LogPlainEvent;
      button.KeyPress += LogPlainEvent;
      button.MouseClick += LogPlainEvent;
      Form form = new Form();
      form.AutoSize = true;
      form.Controls.Add(button);
      Application.Run(form);
      
    • 委派回傳值的反變數(Covariance of delegate return types),也就是是否為上層型別可否轉型,以下範例表示MemoryStream可轉型成Stream,但反過來就不行
    • delegate Stream StreamFactory();
      static MemoryStream GenerateSampleData()
      {
         byte[] buffer = new byte[16];
         for (int i = 0; i < buffer.Length; i++)
         {
            buffer[i] = (byte) i;
         }
         return new MemoryStream(buffer);
      }
      StreamFactory factory = GenerateSampleData;
      using (Stream stream = factory())
      {
         int data;
         while ((data = stream.ReadByte()) != -1)
         {
            Console.WriteLine(data);
         }
      }
      
    • 使用匿名方法的委派實作(Inline delegate actions with anonymous methods) 
      • 處理一個參數(action on  a parameter),運用泛型委派Action<T>
      • Action<string> printReverse = delegate(string text)
        {
           char[] chars = text.ToCharArray();
           Array.Reverse(chars);
           Console.WriteLine(new string(chars));
        };
        Action<int> printRoot = delegate(int number)
        {
           Console.WriteLine(Math.Sqrt(number));
        };
        Action<IList<double>> printMean = delegate(IList<double> numbers) {
           double total = 0;
           foreach (double value in numbers)
           {
              total += value;
           Console.WriteLine(total / numbers.Count);
        };
        printReverse("Hello world");
        printRoot(2);
        printMean(new double[] { 1.5, 2.5, 3, 4.5 });
        
      • 前面範例的匿名方法有點繁瑣,我們在用更精簡的方式
      • List<int> x = new List<int>();
        x.Add(5);
        x.Add(10);
        x.Add(15);
        x.Add(20);
        x.Add(25);
        x.ForEach(delegate(int n) { 
            Console.WriteLine(Math.Sqrt(n));
        });
        
      • 匿名方法回傳值,第一個範例我們用C#2內建的Predicate來實作判斷值回傳true/false,第二個範例我們一樣用C#2的Comparison來實作排序的比較
      • Predicate isEven = delegate(int x) { return x % 2 == 0; };
        Console.WriteLine(isEven(1));
        Console.WriteLine(isEven(4));
        
        static void SortAndShowFiles(string title, Comparison sortOrder)
        {
            string[] fileName = Directory.GetFiles(@"C:\");
            FileInfo[] files = new FileInfo[fileName.Length];
            //
            for (int i0 = 0; i0 < fileName.Length; i0++)
            {
                files[i0] = new FileInfo(fileName[i0]);
            }
            Array.Sort(files, sortOrder);
            //
            Console.WriteLine(title);
            foreach (FileInfo file in files)
            {
                Console.WriteLine(" {0} ({1} bytes)", file.Name, file.Length);
            }
        }
        SortAndShowFiles("Sorted by name:", delegate (FileInfo f1, FileInfo f2) {
            return f1.Name.CompareTo(f2.Name);
        });
        SortAndShowFiles("Sorted by length:", delegate (FileInfo f1, FileInfo f2) {
            return f1.Length.CompareTo(f2.Length);
        });
        
      •  忽略委派的參數,利用前面Button的範例再來改寫一下,用匿名方法不帶參數的方式,大大的簡化了程式碼
      • Button button = new Button();
        button.Text = "Click me";
        button.Click += delegate { Console.WriteLine("LogPlain"); };
        button.KeyPress += delegate { Console.WriteLine("LogKey"); };
        button.MouseClick += delegate { Console.WriteLine("LogMouse"); };
        Form form = new Form();
        form.AutoSize = true;
        form.Controls.Add(button);
        Application.Run(form);
        
    • 在匿名方法裡抓取變數(Capturing variables in anonymous methods)
      • 這邊所代表意思就是在定義匿名方法時有用到外層的變數,因為執行的階段不一樣所以會導致變數可能在某個時間點被變更,匿名方法要等到被執行時才抓取當下的變數值,如以下範例
      • string captured = "before x is created";
        MethodInvoker x = delegate
        {
            Console.WriteLine(captured);
            captured = "changed by x";
        };
        captured = "directly before x is invoked";
        x();
        Console.WriteLine(captured);
        captured = "before second invocation";
        x();
        //執行這段城市會得到以下的結果
        directly before x is invoked
        changed by x
        before second invocation
        
      • 再來看點複雜的範例,如果有變數是在內部宣告及外部宣告時的情境,因為在for迴圈裡宣告變數時會視為各迴圈的獨立變數個體,所以傳進委派方法時都會是獨立的,下面範例foreach會輸出0,10,20,30,40,再來分別是1,2,3 11
      • List<methodinvoker> list = new List<methodinvoker>();
        for (int index = 0; index < 5; index++)
        {
            int counter = index * 10;
            list.Add(delegate
            {
                Console.WriteLine(counter);
                counter++;
            });
        }
        foreach (MethodInvoker t in list)
        {
            t();
        }
        list[0]();
        list[0]();
        list[0]();
        //
        list[1]();
        
  4. 簡單實作迭代器(Implementing iterators the easy way)
    • C# 1.0必須自己刻出iterators的實作,要自行記錄狀態,如MoveNext、Current、Reset等,非常之麻煩。
    • C# 2.0提供yield的方式來達到iterators的實作,大大的減少瑣碎的程式碼撰寫
      • 來看個範例,這個範例主要是顯示出iterators的執行過程 
        static readonly string Padding = new string(' ', 30);
        static IEnumerable<int> CreateEnumerable()
        {
            Console.WriteLine("{0}Start of CreateEnumerable()", Padding);
            for (int i = 0; i < 3; i++)
            {
                Console.WriteLine("{0}About to yield {1}", Padding, i);
                yield return i;
                Console.WriteLine("{0}After yield", Padding);
            }
            Console.WriteLine("{0}Yielding final value", Padding);
            yield return -1;
            Console.WriteLine("{0}End of CreateEnumerable()", Padding);
        }
        IEnumerable<int> iterable = CreateEnumerable();
        IEnumerator<int> iterator = iterable.GetEnumerator();
        Console.WriteLine("Starting to iterate");
        while (true)
        {
            Console.WriteLine("Calling MoveNext()...");
            bool result = iterator.MoveNext();
            Console.WriteLine("... MoveNext result={0}", result);
            if (!result)
            {
                break;
            }
            Console.WriteLine("Fetching Current...");
            Console.WriteLine("... Current result={0}", iterator.Current);
        }
        
        有幾點要注意幾個地方
        1. CreateEnumerable執行直到MoveNext才開始執行方法裡的程式
        2. 執行Current不會執行任何程式
        3. yeild return執行完就停住直到下次MoveNext
        4. 一個方法可以有多個yeild return
        5. 方法會直到MoveNext為false才會結束
      • 它可以在執行過程中中斷它,yeild break,另外還可以在裡面下try..Catch..Finally,如果不是用yeild break而是直接return做停止執行iterators時finally理論上不會執行,但他會等到最後離開foreach時執行,這是比較特別的地方
      • 這邊有幾點要了解的
        1. 在第一次執行MoveNext之前,取得Current會返回型別的預設值
        2. MoveNext返回false後,Current會儲存最後一個值
        3. Reset是不實作的
    • 真實使用iterators,這邊作者列了3個例子
      • 時刻表中的日期
      • static IEnumerable<DateTime> DateRange
        {
            get
            {
                for (DateTime day = new DateTime(2016,6,3); day <= new DateTime(2016, 07, 01); day = day.AddDays(1))
                {
                    yield return day;
                }
            }
        }
        foreach (var item in DateRange)
        {
            //列出日期
            var a = item;
        }
        
      • 列出文件
      • foreach (var item in ReadLines("C:/Programing/test.txt"))
        {
            string a = item;
        }
        static IEnumerable<string> ReadLines(Func<TextReader> provider)
        {
            using (TextReader reader = provider())
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    yield return line;
                }
            }
        }
        static IEnumerable<string> ReadLines(string filename)
        {
            return ReadLines(delegate {
                return File.OpenText(filename);
            });
        }
        
      • 利用謂詞作篩選
      • IEnumerable<string> lines = ReadLines("C:/Programing/test.txt");
        Predicate<string> predicate = delegate (string line)
            { return line.StartsWith("abc"); };
        foreach (string line in Where(lines, predicate))
        {
            Console.WriteLine(line);
        }
        public static IEnumerable<T> Where<T>(IEnumerable<T> source,
            Predicate<T> predicate)
        {
            if (source == null || predicate == null)
            {
                throw new ArgumentNullException();
            }
            return WhereImpl(source, predicate);
        }
        private static IEnumerable<T> WhereImpl<T>(IEnumerable<T> source,
            Predicate<T> predicate)
        {
            foreach (T item in source)
            {
                 if (predicate(item))
                 {
                     yield return item;
                 }
            }
        }
        
  5. 剩下的C# 2.0的新特性(Conculding C# 2 the final features)
    • partial types部分類型,就是class及method可以建立成部分,可分布在不同的檔案,通常是用在有程式碼產生器產出的類別中可以讓程式設計師惡外加東西在類別上,這是就可另外建立partial避免程式碼被洗掉,或是有需要分工的部分也可以用partial做區分;在C# 3.0有多增加一個特性,就是假設在partial裡只有宣告但沒指定method的實作時編譯器會忽略此段code,避免程式碼出錯,如以下範例
    • // Generated.cs
      using System;
      partial class PartialMethodDemo
      {
          public PartialMethodDemo()
          {
              OnConstructorStart();  //這段在編譯器就看不到
              Console.WriteLine("Generated constructor");
              OnConstructorEnd();
          }
          partial void OnConstructorStart();
          partial void OnConstructorEnd();
      }
      // Handwritten.cs
      using System;
      partial class PartialMethodDemo
      {
          partial void OnConstructorEnd()
          {
              Console.WriteLine("Manual code");
          }
      }
      
    • static class靜態類別,在C# 1.0時只有靜態方法,靜態類別有幾點要注意
      • 不可宣告為抽象類別(abstract)及密封類別(sealed)
      • 不可繼承介面並實作類別 
      • 不可有任何非靜態的方法,包含建構子
      • 不可以有運算子
      • 不可有protected或protected internal
    • 獨立的getter/setter 屬性,C# 1.0不能定義public get / private set
    • 命名空間別名,如果有相同的名字在不同的命名空間時可使用
      • C# 2.0 增加 :: 別名的使用
      • using System;
        using WinForms = System.Windows.Forms;
        using WebForms = System.Web.UI.WebControls;
        class WinForms {}
        class Test {
            static void Main()
            {
                Console.WriteLine(typeof(WinForms::Button));
                Console.WriteLine(typeof(WebForms::Button)); 
            }
        }
        
      • 使用global::,例如有兩個class,一個有命名空間另一個沒有的情況下可使用
      • using System;
        class Configuration {}
        namespace Chapter7
        {
            class Configuration {}
            class Test {
                static void Main()
                {
                    Console.WriteLine(typeof(Configuration)); 
                    Console.WriteLine(typeof(global::Configuration)); 
                    Console.WriteLine(typeof(global::Chapter7.Test));
                }
            } 
        }
        
      • 外部別名(Extern allases),如引用兩個外部類別庫都有相同的命名空間及方法時可使用
      • // Compile with
        // csc Test.cs /r:FirstAlias=First.dll /r:SecondAlias=Second.dll
        extern alias FirstAlias;
        extern alias SecondAlias;
        using System;
        using FD = FirstAlias::Demo;
        class Test 
        {
            static void Main()
            {
                Console.WriteLine(typeof(FD.Example));
                Console.WriteLine(typeof(SecondAlias::Demo.Example));
            }
        }
        
    • 使用Pragma指令
      • 在VS撰寫程式碼時常常會有一些警告訊息,如變數未使用等,通常都會去修正不正確的地方,Pragma的指令是讓你可以暫時忽略這些警告,比如像以下的範例
      • public class FieldUsedOnlyByReflection {
        #pragma warning disable 0169
            int x;
        #pragma warning restore 0169
        }
        
    • 非安全程式碼中的固定暫存大小(Fixed-size buffers in unsafe code)
      • 可以使用fixed陳述式,在資料結構中建立具有固定大小陣列的緩衝區
      • private fixed char name[30];
        
    • 把內部的成員暴露給指定的類別庫(Exposing Internal members to selected assembles)
      • 利用授權方式讀取
      • // Compiled to Source.dll
        using System.Runtime.CompilerServices; 
        [assembly:InternalsVisibleTo("FriendAssembly")] 
        public class Source
        {
           internal static void InternalMethod() {}
           public static void PublicMethod() {}
        }
        // Compiled to FriendAssembly.dll
        public class Friend
        {
           static void Main()
           {
              Source.InternalMethod();
              Source.PublicMethod();
           }
        }
        // Compiled to EnemyAssembly.dll
        public class Enemy
        {
           static void Main()
           {
              // Source.InternalMethod();
              Source.PublicMethod();
           }
        }
        
      • 通常會使用到他的地方是在寫單元測試的時候,但也不是必要的
    • 這邊提到剩下的C# 2.0新增的特性也不是非常必要或是常用的,但有需要時可以使用.

2016年6月3日 星期五

升級CrystalReport遇到的狀況

昨天升級完.NET從2.0到3.5,想說應該都OK了,結果測一下報表發現有錯誤訊息,錯誤點在於要將ReportSource指到CrystalReportSource.ReportDocument.Load時發生問題,訊息如下"為具有 CLSID {5FF57840-5172-4482-9CA3-541C7878AE0F} 的元件擷取 COM Class Factory 失敗: 800736b1",看到這訊息直覺就是元件沒安裝好,但又覺得那悶,我裝了之前從官網上下載的CrystalReport安裝檔For VS2008的,檔名是CrystalReports2007_x86.msi(印象中之前裝好像可以用的說),後來又用手動把元件重新註冊好像還是一樣,最後想到在安裝VS2008時,安裝程式都會把CrystalReport的安裝檔放在電腦的某個地方,位置在C:\Program Files (x86)\Microsoft SDKs\Windows\v6.0A\Bootstrapper\Packages\CrystalReports10_5,裡面有x86跟x64的版本,想說試看看用這個版本裝到Server上,結果就正常啟動了,呼~~,終於解決了