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

2014年8月17日 星期日

一秒看破 C# Attribute

...我們絕非從未謀面

...總是悄悄的守候在我們身旁

...默默犧牲奉獻幫我們做了許多事

他是我們 最熟悉的陌生人

~ C# Attribute ~





此屬性 (Attribute) 非彼屬性 (Property)

對岸管他叫 "特性"

這裡還是叫他 Attribute 吧

M$ 的說明:

C# 提供了一個定義宣告式標記 (Tag) 的機制,此宣告標記稱為屬性 (Attribute),可以讓您在原始程式碼中放置一些實體,來指定額外的資訊。屬性所包含的資訊可以在 Run Time 時透過反映來擷取。您可以使用預先定義屬性 (Attribute)或者您可以定義自己的自訂屬性 http://msdn.microsoft.com/zh-tw/library/aa288059(v=vs.71).aspx





我們無時無刻都能瞥見 Attribute 的身影

加上 Attribute 我們的程式就被賦予了超能力

但是...Why?

為什麼明明我們什麼都沒做 只是加上 Attribute 程式就會多出一些能力呢?





答案顯而易見 自然是冥冥之中有股神秘的力量 Framework 幫我們完成了這些事

在 C# 物件導向 裡面通常都是靠 [繼承] 承接或擴充功能

但是[繼承]是一種垂直鏈狀的概念

當[繼承]深度越深的時候[可維護性]相對的就會變低(因為太多層了)

所以有句口訣叫做 多用合成 少用繼承

設法把不同功能拆分成 單一職責(Single Responsibility Principle)的小類別

再合體成模組級別的大類別 (怎麼合體自然也有一堆花招 EX:DI、IOC etc)

但是這僅止於 設計類別時內部自嗨的層面

有的時候需求來自是外部的





例如有個[顧客]類別有其自身的屬性方法

另外還有[店家]類別

[店家]類別自然無權干涉[顧客]類別的內部行為

理所當然[店家]對[顧客]提供服務

即便[店家]對外宣稱:我們無分男女、宗教、種族、階級、黨派,在服務上一律平等

但是用膝蓋骨想就知道那肯定是ㄊㄇ唬爛的

所以[店家]類別需要針對不同的[顧客]提供不同等級的服務

或許有人覺得那就給[顧客]一個叫做[顧客等級]的 Property 就能解決了

事實上的確可行 但是卻不是很合理

因為 Property 是屬於類別內部擁有的資訊 由類別決定如何提供給外界使用

如果[顧客]可以任意決定自身的[顧客等級]那未免也太可笑

這個時候應該使用 Attribute

Attribute 主要是給外部使用的資訊 LIKE 設定 貼標籤 註解 表述

以便[店家](外部)在使用[顧客](貼 Attribute 的類別)的時候能夠達成[提供不同等級的服務]的需求





講了那麼多 我想還是沒人懂 Attribute 到底在玩什麼把戲 (因為這就是 Attribute 的魅力特色呀 <3)

馬上來玩玩就對了





首先有兩個頁面 External 跟 Internal

分別使用 ExternalBulletin 跟 InternalBulletin

共用的部分繼承抽象類 Bulletin

Bulletin 在建構子 會使用 ArticlesFactory 來產生文章

Articles POPO 類 單純 DTO

如今響應個資法 需要 Articles 裡面的人名都改成 OO

這是一個外部因素帶來的改變 實際上 Articles 的本質不應該被改變 只是呈現的時候 人名必須要變成 OO

所以這裡設計一個 SilencerAttribute

裡面有個 SilencerMode 用來決定模式

現在可以給 ExternalBulletin 或 InternalBulletin 掛上 Attribute 了

基本上要掛哪個都可以(水平特性)

只是情境上是 外部的 Bulletin 需要保密

而內部的 Bulletin 則需要看到原始 Articles

只是掛上 Attribute 會怎樣嗎? 會有什麼事發生嗎??

用肋骨想就知道 當然不會怎麼樣 什麼鬼事也不會發生(鬼月的話就不敢保證)

Attribute 只是掛好看的標籤 自然是需要冥冥之的股神秘力量額外的類別來看這些 Attribute 做對應的變化 才會有效果

聰明的看官想必也早知道謎底了 就是 ArticlesFactory 要來幹這件事(因為[工廠]就是要[黑箱]作業呀)

這裡就能看到 ArticlesFactory 透過神奇的演算法(?) 巧妙的過濾人名 改成OO

結果顯而易見 就是 Articles 被改成OO 沒啥好講的





現在就能明白什麼是[水平擴充]了

水平層面上的 [類別] 或 [方法] 都能任意的掛上 Attribute 來擴充功能

抽換 Attribute 都不會影響該 [類別] 或 [方法] 內部的運作

但是對於 外部(會來看這些 Attribute 的 [類別] 或 [方法])來說 又能 [提供不同等級的服務]





這貨很神呀 根本可以 單排上金 的感覺 沒有缺點嗎?





缺點自然是有的

使用 Attribute 難以偵錯 因為你可能不知道 到底是誰在使用這些 Attribute

像是這個範例 是由 ArticlesFactory 來使用 Attribute 做判斷

如果並不清楚 ArticlesFactory 會使用 Attribute 的情況下

得到的 Articles 就會不如預期

可能就無法得知 到底為什麼 人名變成 OO 了

明明 CODE (垂直) 裡面都沒有寫到這段功能

這個情況在 分別開發的時候 會更顯著





那什麼時候適合使用 Attribute 呢?

.NET MVC 就是很好的示範了

Authorize HttpPost AllowHtml OutputCache

這些不影響 Controller 內部邏輯的功能 就能抽出來變成 Attribute 交由第三方 (Framework) 完成實作





最後 Attribute 還有個屌炸天的應用

極度推薦!

2013年12月31日 星期二

LINQ 封裝 "computed" properties 五行封印術 - 看技術文章學英文第二彈 - 跨年特別篇


警告:此篇為翻譯文章,所有內容皆為原作者所有
此外歡迎指正破英文,如果哪邊有嚴重謬誤哪肯定是我把靈魂出賣給了 google 小姐 ... 這篇好難翻呀 我日 -.-




警告:此篇文章 內容艱澀 技術過硬
這篇 100% 是技術人員寫的 英文用字的 可讀性 可維護性 就跟"大部分的專案" 一樣好




這篇文章的

server 指得是 SQL SERVER 這類的東東

client 指得是 自定義的 C# 代碼

translation 指得是 將 C# 轉成 查詢端語法





客戶端 自訂屬性 和 遠端 LINQ provider


如果你寫了些自訂屬性 你將無法在 遠端 LINQ 操作中 使用他們 (EX:LINQ to Entities)

問題發生的原因在於 這些客製屬性無法 轉譯到 server (EX:SQL SERVER)

因為他們已經被編譯成 中介語言 (intermediate language - IL)

而不是 個 需要被 IQueryable 實作 translation 的 LINQ 表達式樹 (LINQ expression trees)

目前在 .NET 是沒法度 用些反向工程 將 IL 變回方法 (methods) 和 語法 (syntax)

讓我們可以如預期的轉換成查詢語法 (remote query)

這意味者你不得不將查詢寫成兩個部分

首先是 server (EX:SQL SERVER) 可以幹的 (LINQ 查詢)

再用 ToList 或者 AsEnumerable 查詢出中間結果 到 client

然後就只能在這邊進行些運算操作

如果你想要 激烈的 做些轉換 這會非常耗損效能

我們 (David, Colin Meek and myself) 想出了一個 非關 provider 一次性宣告 屬性的方法

這樣他們就可以在兩種情況下使用

他能在 LINQ to SQL, LINQ to Entities 和任何其他 LINQ 輕易的運算屬性

在 .NET 3.5 SP1 運作良好 :)


使用前



這邊我們 擴充 Employee 類別 增加 Age 和 FullName

我們只需要 名字帶有 "da" 的人

但是為了 做出選取 我們不得不 抓下所有東東 到 client


partial class Employee {
 public string FullName {
  get { return Forename + " " + Surname; }
 }

 public int Age {
  get { return DateTime.Now.Year - BirthDate.Year -
   (((DateTime.Now.Month < BirthDate.Month)
   || DateTime.Now.Month == BirthDate.Month && DateTime.Now.Day < BirthDate.Day) ? 1 : 0));
  }
 }
}
...
var employees = db.Employees.ToList().Where(e => e.FullName.Contains("da")).GroupBy(e => e.Age);


使用後


這邊 使用我們的方式

他發生在 server 端...

而且 LINQ to SQL, LINQ to Entities 都行的通


partial class Employee {
    private static readonly CompiledExpression fullNameExpression
     = DefaultTranslationOf.Property(e => e.FullName).Is(e => e.Forename + " " + e.Surname);
    private static readonly CompiledExpression ageExpression
     = DefaultTranslationOf.Property(e => e.Age).Is(e => DateTime.Now.Year - e.BirthDate.Value.Year - (((DateTime.Now.Month < e.BirthDate.Value.Month) || (DateTime.Now.Month == e.BirthDate.Value.Month && DateTime.Now.Day < e.BirthDate.Value.Day)) ? 1 : 0)));

    public string FullName {
        get { return fullNameExpression.Evaluate(this); }
    }

    public int Age {
        get { return ageExpression.Evaluate(this); }
    }
}
...
var employees = db.Employees.Where(e => e.FullName.Contains("da")).GroupBy(e => e.Age).WithTranslations();


使用注意事項


上述技巧的注意事項是 你需要確保 你的類別 在查詢之前 有初始化 (看看下面的替代方案)

然而 很顯然的 你註冊的表達式(expression) 所指的 屬性(property) 必須能夠被 translated

所以你將需要約束你自己的方法(methods) 和 實作 IQueryable


這裡是一些上述例子的幾個替代方案。


註冊 表達式


你可以像這個例子一樣 在類別本身的註冊屬性

該屬性可以不靠任何反射 (reflection) 就能自行 計算表達式 (evaluate the expressions)

此外 如果 效能考量不是那麼重要的話

你可以在其他地方註冊它們

使用 方法 (methods) 透過反射 動態查看他們的值

例如
...
DefaultTranslationOf.Property(e => e.FullName).Is(e => e.Forename + " " + e.Surname);
var employees = db.Employees.Where(e => e.FullName.Contains("da")).GroupBy(e => e.Age).WithTranslations();
...
partial class Employee {
    public string FullName { get { return DefaultTranslationOf.Evaluate(this, MethodInfo.GetCurrentMethod());} }
}




如果 效能考量很重要

你可以獲得 重複運算的 完整代碼

一次從中介語言中的屬性 然後一次從 translation 的表達示

針對不同情境的不同對應


有時候 你的程式的某些部份 可能想在不同的情境下

使用不同的 translations 或 performance 等等

沒問題!

WithTranslations 方法 通常操作著預設的 translation 對應 (map)

(靠 DefaultTranslationOf 訪問)

但也有另一個使用 TranslationMap 的多載

讓你對付特定的情況

例如
var myTranslationMap = new TranslationMap();
myTranslationMap.Add(e => e.Name, e => e.FirstName + " " + e.LastName);
var results = (from e in db.Employees where e.Name.Contains("martin") select e).WithTranslations(myTranslationMap).ToList();



他是怎麼運作的


CompiledExpression<T, TResult>

我們需要做的第一件事是讓 自訂編寫的「計算」屬性

遠離 IL 然後 變回 表達式樹 (expression trees)

所以我們才能 translate 它們

因為我們也想 在 client 計算他們

所以我們需要在 執行時期 編譯他們

CompiledExpression 只是為了取得 表達式 Func<T, TResult> 而存在

允許編譯和計算 objects 針對不同的編譯版本

ExpressiveExtensions


這個小類別 提供 WithTranslations 擴充方法 和 internal TranslatingVisitor

以利走訪 透過 TranslationMap 實際註冊 Func<T, TResult> expressions 的 property

所以 底層的 LINQ provider 可以處理他

TranslationMap


我們需要有 編譯 表達式的 屬性對應

TranslationMap 為此存在

你可以手動建立一個 TranslationMap

將其傳入 WithTranslations

如果你想要依靠程式 在執行時期建立它們 或者 對不同情境使用不同方法

但是通常你會想用它...

DefaultTranslationOf


這個 helper 類別 讓你針對 default TranslationMap 註冊屬性

我們在 WithTranslations 沒有傳入值的時候使用

它也可以讓你查看 已註冊的屬性

所以你也可以計算他們

即使它耗費了一點反射的效能
public int Age { get { return DefaultTranslationOf.Evaluate(this, MethodInfo.GetCurrentMethod()); } }




有樂!


[)amien











最後...新年快樂 2014

2013年10月21日 星期一

第一次互毆就上手 Swap Between Both

有時候 我們會遇到互毆的需求

像是我們要讓 A 跟 B 互毆

印出

AAA hit BBB!!!

BBB hit AAA!!!





這麼簡單一片蛋糕都不到的難度

            Member memberA = new Member { ID = 1, Name = "AAA" };

            Member memberB = new Member { ID = 2, Name = "BBB" };

            Debug.WriteLine(string.Format("{0} hit {1}!!!", memberA.Name, memberB.Name));


            Debug.WriteLine(string.Format("{0} hit {1}!!!", memberB.Name, memberA.Name));




但是別忘了 需求有時候是 會長大的幸福

重複代碼不說 有時候遇到邏輯有點兇殘的時候

Wo Ta Ma 的有時候忘了 現在到底誰是誰? 誰是攻誰是受? 誰又要對誰做什麼





此時我們冷靜的來瞧瞧有啥蛛絲馬跡

1.兩個人(物件)要做同一件事 例如 都是要打人

2.唯一的差異是 立場不同 A要打B B要打A

3.先後順序不是很重要 但是兩個人要確實都賞對方一拳





原先的代碼有什麼缺點?

1.相似內容重複代碼

2.不夠直覺 邏輯複雜時難以釐清兩者關係





若是有一個方法可以使用 我們希望他長什麼樣子?

1.泛型是基本 畢竟要什麼都吃

2.同樣的動作 我只想做一遍

3.其實當下我只在乎 哪個主動 哪個被動 骨子裡誰是誰 誰又是誰 我 TMD 一點也不在乎





綜合以上結論這個方法大概就長這樣

        public void Main()
        {
            Member memberA = new Member { ID = 1, Name = "AAA" };

            Member memberB = new Member { ID = 2, Name = "BBB" };

            SwapBetweenBoth<Member>(
                (from, to) =>
                {
                    Debug.WriteLine(string.Format("{0} hit {1}!!!", from.Name, to.Name));
                },
                memberA,
                memberB
            );
        }

        public void SwapBetweenBoth<T>(Action<T, T> action, T one, T another)
        {
            T[] array = { one, another };
            for (int i = 0; i < 2; i++)
            {
                T from = array[i];

                T to = array[(i + 1) % 2];

                action(from, to);
            }

        }




或許有人會問 這樣真的有比較好嗎?

但是至少自我感覺良好呀 哈哈哈

2013年5月31日 星期五

一秒看破 LINQ & Lambda Expressions

最近有人問我 令Q 跟 浪打 到底是在玩什麼花招的? 可以吃嗎? 非用不可?

我就在此稍微回答一下

1.令Q 跟 浪打 都可以用來對付 [可列舉型態型別的查詢] 浪打花招更多一點點 還能用來建立 委派 跟 註冊事件

2.可以吃唷 前者有點 QQ 的 後者 有點辣辣的

3.就民主立場而言 沒有非用不可 跟你有沒有非要 非死不可 差不多

 但是專制獨裁的微軟要推他就會使出各種陰謀稱作技術主流





如果這張圖你很懂在幹嘛 你可以關閉這個網頁 試著啟動 OO聯盟

影像節錄自 跟著 Wade 哥哥 學習ASP.NET MVC + NHibernate - Day 5 16:43



如果 你覺得 花惹發科 就繼續看下去

其實上圖跟等下的內容沒有關係 呵呵 但是證明了 微軟會無所不用其極的在任何地方使用他





首先 非關DB 非關Query List 的 方法

如果你不認識 泛型先生 跟 委派小姐 的話 請拜估狗大神

生死格鬥之浪打委派 第一彈 List Sort()、Find()、FindAll()、Exist() 豬走路範例
public void Sort(Comparison<T> comparison);

public T Find(Predicate<T> match);

public List<T> FindAll(Predicate<T> match);

public bool Exists(Predicate<T> match);





發現了什麼了嗎? 沒錯 浪打 就是一個委派 委派就是一個方法 所以使用浪打 跟使用一般的方法只差在多寫一個 方法

(這句話其實不完全正確 但是暫時這麼想會比較好理解)





以前老師說 一般的 = 在C# 是向左指派的意思 而 => 則是 向右指派 所以 x 可以被 => 右邊所用...

恩 完全正確 但是當時我也 完全不知道這是在講沙米碗糕 真是給他打敗啦 還是說只有我比較笨 聽不懂上面這句話





實際玩弄很久之後 才大徹大悟 哎呀 他不過是濃縮寫法的方法

=> 的左邊 就是方法傳入參數 會由叫用委派的地方提供

而且會自動推斷型別 所以不像一般方法需要 給傳入參數標明型別

=> 的右邊 就是方法要執行的內容

一行寫法 可以省略 { } return 跟 ;

整個浪打本身 就是一個匿名方法 所以不像一般方法需要命名


沒傳入參數 沒回傳型別 浪打
        private void Foo()
        {
            Action Fn = () => { };
        }
沒傳入參數 沒回傳型別 方法
        private void Fn()
        {
        }

有傳入參數 沒回傳型別 浪打
        private void Foo()
        {
            Action<int> Fn = i => { };
        }
有傳入參數 沒回傳型別 方法
        private void Fn(int i)
        {
        }

沒傳入參數 有回傳型別 浪打
        private void Foo()
        {
            Func<bool> Fn1 = () => true;
            Func<bool> Fn2 = () => { return true; };
        }
沒傳入參數 有回傳型別 方法
        private bool Fn()
        {
            return true;
        }

有傳入參數 有回傳型別 浪打
        private void Foo()
        {
            Func<int, bool> Fn1 = i => i > 0;
            Func<int, bool> Fn2 = i => { return i > 0; };
        }
有傳入參數 有回傳型別 方法
        private bool Fn(int i)
        {
            return i > 0;
        }




什麼? 你說 LINQ 跟查詢在哪? 下回分曉 哈哈

2013年3月28日 星期四

mongodb in ASP.NET 之 第一次切芒果就上手

mangle...





喔耶~ NoSQL

喔耶~ BSON 百度

喔耶~ mongodb Wiki





要是以為我會寫介紹文 就太傻太天真

直接玩弄





截圖





testQuery.aspx

    <form id="form1" runat="server">
        name:<asp:TextBox ID="name_tb" runat="server"></asp:TextBox>
        age:<asp:TextBox ID="ageHead_tb" runat="server"></asp:TextBox>
        ~<asp:TextBox ID="ageEnd_tb" runat="server"></asp:TextBox>
        <asp:CheckBox ID="male_cb" runat="server" Text="Male" />
        <asp:CheckBox ID="female_cb" runat="server" Text="Female" />
        <asp:Button ID="Query" runat="server" Text="Query" OnClick="Query_Click" />
        <asp:GridView ID="show_gv" runat="server" AutoGenerateColumns="False">
            <Columns>
                <asp:BoundField DataField="_id" HeaderText="_id" />
                <asp:BoundField DataField="name" HeaderText="name" />
                <asp:BoundField DataField="age" HeaderText="age" />
                <asp:BoundField DataField="gender" HeaderText="gender" />
            </Columns>
        </asp:GridView>
    </form>




testQuery.aspx.cs

using System;
using System.Linq;
using MongoDB.Driver;
using MongoDB.Driver.Linq;

namespace testMongo
{
    public partial class testQuery : System.Web.UI.Page
    {
        private MongoCollection memberCollection = GetMemberCollection();

        protected void Query_Click(object sender, EventArgs e)
        {
            bool ckMale, ckFemale;
            if (
                (!male_cb.Checked && !female_cb.Checked) ||
                (male_cb.Checked && female_cb.Checked)
            )
            {
                ckMale = true;
                ckFemale = false;
            }
            else
            {
                ckMale = ckFemale = male_cb.Checked;
            }
            int ageHead =
                string.IsNullOrEmpty(ageHead_tb.Text)
                ? 0
                : Convert.ToInt32(ageHead_tb.Text);
            int ageEnd =
                string.IsNullOrEmpty(ageEnd_tb.Text)
                ? int.MaxValue
                : Convert.ToInt32(ageEnd_tb.Text);
            IQueryable q = memberCollection.AsQueryable<Member>().Where(
                x =>
                    x.name.Contains(name_tb.Text) &&
                    x.age >= ageHead &&
                    x.age <= ageEnd &&
                    (
                        x.gender == ckMale ||
                        x.gender == ckFemale
                    )
            );
            IMongoQuery mongoQuery = (q as MongoQueryable<Member>).GetMongoQuery();
            show_gv.DataSource = memberCollection.FindAs<Member>(mongoQuery);
            show_gv.DataBind();
        }

        private static MongoCollection<Member> GetMemberCollection()
        {
            MongoServer server = MongoServer.Create("mongodb://192.168.7.25:27017");
            MongoDatabase database = server.GetDatabase("test");
            MongoCollection<Member> member = database.GetCollection<Member>("member");
            return member;
        }
    }
}




截圖





testSaveMember.aspx

    <form id="form1" runat="server">
        name:<asp:TextBox ID="name_tb" runat="server"></asp:TextBox>
        age:<asp:TextBox ID="age_tb" runat="server"></asp:TextBox>
        <asp:RadioButton ID="male_rb" runat="server" Checked="True" GroupName="Gender" Text="Male" />
        <asp:RadioButton ID="female_rb" runat="server" GroupName="Gender" Text="Female" />
        <asp:Button ID="insert" runat="server" OnClick="insert_Click" Text="Insert" />
        <br />
        _id:<asp:TextBox ID="id_tb" runat="server"></asp:TextBox>
        <asp:Button ID="update" runat="server" Text="Update" OnClick="update_Click" />
        <asp:Button ID="delete" runat="server" Text="Delete" OnClick="delete_Click" />
        <asp:GridView ID="show_gv" runat="server" AutoGenerateColumns="False">
            <Columns>
                <asp:BoundField DataField="_id" HeaderText="_id" />
                <asp:BoundField DataField="name" HeaderText="name" />
                <asp:BoundField DataField="age" HeaderText="age" />
                <asp:BoundField DataField="gender" HeaderText="gender" />
            </Columns>
        </asp:GridView>
    </form>




testSaveMember.aspx.cs


using System;
using System.Linq;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;

namespace testMongo
{
    public partial class testSaveMember : System.Web.UI.Page
    {
        private MongoCollection memberCollection = GetMemberCollection();

        protected void Page_Load(object sender, EventArgs e)
        {
            if (IsPostBack) return;
            BindGridViwe();
        }

        private void BindGridViwe()
        {
            show_gv.DataSource = memberCollection.FindAllAs(typeof(Member));
            show_gv.DataBind();
        }

        protected void insert_Click(object sender, EventArgs e)
        {
            SaveMember(
                name: name_tb.Text,
                age: int.Parse(age_tb.Text),
                gender: male_rb.Checked
            );
        }

        protected void update_Click(object sender, EventArgs e)
        {
            SaveMember(
                _id: new ObjectId(id_tb.Text),
                name: name_tb.Text,
                age: int.Parse(age_tb.Text),
                gender: male_rb.Checked
            );
        }

        protected void delete_Click(object sender, EventArgs e)
        {
            DeleteMember(new ObjectId(id_tb.Text));
            BindGridViwe();
        }

        private void SaveMember(string name, int age, bool gender)
        {
            SaveMember(ObjectId.Empty, name, age, gender);
        }

        private void SaveMember(ObjectId _id, string name, int age, bool gender)
        {
            Member member = new Member { _id = _id, name = name, age = age, gender = gender };
            memberCollection.Save(member);
            BindGridViwe();
        }

        private void DeleteMember(ObjectId _id)
        {
            IQueryable q = memberCollection.AsQueryable<Member>().Where(x => x._id == _id);
            IMongoQuery mongoQuery = (q as MongoQueryable<Member>).GetMongoQuery();
            memberCollection.Remove(mongoQuery);
        }

        private static MongoCollection<Member> GetMemberCollection()
        {
            MongoServer server = MongoServer.Create("mongodb://192.168.7.25:27017");
            MongoDatabase database = server.GetDatabase("test");
            MongoCollection<Member> member = database.GetCollection<Member>("member");
            return member;
        }
    }
}