C#學習筆記

命名規則

https://blog.csdn.net/menglanyingfei/article/details/74992558html

1.1 Pascal規則來命名屬性、方法、事件和類名

public class HelloWorldjava

 {ios

     public void SayHello(string name)web

     {正則表達式

     }spring

 }數據庫

 

  Pascal規則是指名稱中單詞的首字母大寫 ,如EmployeeSalary、 ConfimationDialog、PlainTextEncoding。編程

 

1.2 Camel規則來命名成員變量、局部變量和方法的參數

 

 

public class Product設計模式

 {api

     private string productId;

     private string productName;

 

     public void AddProduct(string productId,string productName)

     {

     }

 }

 

Camel規則相似於Pascal規則 ,但名稱中第一個單詞的首字母不大寫 ,如employeeSalary、 confimationDialog、plainTextEncoding。

 

1.3 不要使用匈牙利命名法。不要給成員變量加任何前綴(如ms_等等)。若是想要區分局部變量和成員變量,可使用this關鍵字。

1.4 不要將常量或者只讀變量的變量名所有大寫,而使用Pascal規則來命名。

 // Correct

 public static const string ShippingType = "DropShip";

 

 // Avoid

 public static const string SHIPPINGTYPE = "DropShip";

 

1.5 接口的名稱通常以大寫I做前綴。

public interface IConvertible

 {

     byte ToByte();

 }

 

1.6 自定義的屬性以Attribute結尾。

 public class TableAttribute:Attribute

 {

 }

1.7 自定義的異常以Exception結尾。

 public class NullEmptyException:Exception

 {

 

 }

1.8 類的命名。用名詞或名詞短語來命名類名。

 public class Employee

 {

 }

 public class BusinessLocation

 {

 }

 public class DocumentCollection

 {

 }

1.9 方法的命名。通常將其命名爲動賓短語。

 public class File

 {

     public void CreateFile(string filePath)

     {

     }

     public void GetPath(string path)

     {

     }

 }

局部變量的名稱要有意義。不要直接用i, j, k, l, m, n, x, y, z等作變量名,但for循環除外。

全部的成員變量聲明在類的頂端,用一個換行把它和方法分開。同時可使用成對的#region...#endregion標記,方便摺疊。

布爾型變量或者方法通常能夠用is、can或者has作前綴。如,isFinished, canWork等。

通常C#的編碼風格要求花括號{另起一行,不要直接跟在類名和方法後面。

public Sample()

{

   // TODO: 在此處添加構造函數邏輯

}

能夠用縮寫做爲UI元素的前綴。常見UI組件的通常縮寫形式:

Label --> lbl、Text --> txt、Button --> btn

Image --> img、 Widget --> Wgt、 List --> lst、CheckBox --> chk

Hyperlink --> lnk、Panel --> pnl、Table --> tab

ImageButton --> imb

判斷條件是一個布爾變量時不要使用==進行條件判斷。

 

// 不友好的寫法

private bool isFinished = true;

if(isFinished == true)

{

    // ...

}

 

// 正確的寫法

private bool isFinished = true;

if(isFinished)

{

    // ...

}

變量名是一個單詞的儘可能不要縮寫,多單詞組成的變量名可適當縮寫。

在類的頂部聲明全部的成員變量,靜態變量聲明在最前面。

 

// Correct

public class Account

{

    public static string BankName;

    public static decimal Reserves;

 

    public string Number {get; set;}

    public DateTime DateOpened {get; set;}

    public DateTime DateClosed {get; set;}

    public decimal Balance {get; set;}

 

    // Constructor

    public Account()

    {

        // ...

    }

}

若是一個方法超過25行,就須要考慮是否能夠重構和拆分紅多個方法。方法命名要見名知意,好的方法名能夠省略多餘的註釋。方法功能儘可能單一。

 

特殊地方

2.1 using (){}生成的變量會主動disposable(),就是主動釋放資源。

2.2 internal class只在同一個項目內能夠被使用,對外部項目不可見。

只能繼承一個基類,但能夠繼承多個接口。

接口不能實例化,但能夠申明變量使用。

 

 

2.3 抽象類和接口

 

相同點

方法

是否能夠繼承多個

其餘

抽象類

都不能夠被實例化,但能夠聲明變量;

abstract class A{}

 

class B:A{}

 

A aA = new B();

方法的實現體無關緊要;子類可覆蓋抽象父類的方法;方法的訪問限制有publicabstractvirtualinternalprotected internal

子類只可繼承一個父類或抽象類,但能夠繼承多個接口;

均可以有;

接口

方法沒有實現體,子類必須實現;

方法訪問限制只有隱式的public

 

不能夠有成員變量(能夠有屬性),構造和析構函數;靜態方法和常量;

不能夠有static, virtual, abstract, or sealed.

能夠有new,用來隱藏父接口中的實現;

接口能夠做爲類的成員,但不能夠做爲接口的成員;

注意以上指接口類自己的限制,但繼承該接口的普通類,能夠將繼承的接口聲明爲virtual,abstract,但不能爲conststatic

 

2.4 類的成員有三種

 

含義

通用訪問限制()

特殊訪問限制

fields

成員變量,記錄了類對象的狀態信息;

public:公用的,類的內外都可訪問

private:私有的,類內訪問;

internal:項目內的,項目類訪問

protected:本類或子類訪問;

protected internal:項目內的本類或子類訪問;

static: 歸類全部,而非實例全部,因此實例不能調用;

readonly:構造函數或定義是初始化;其餘地方不能賦值;

methods

方法

virtual:子類能夠重定義,亦可直接採用該函數;覆蓋父方法,子類能夠增長override

abstract:該關鍵字只能在抽象類中使用,沒有函數實現體;本方法必須在子類中重寫;相似純虛函數;

override:在子類中使用,表示該函數從父類繼承,在本子類重寫,覆蓋父類方法;Base B = new Inherited ();B調用方法時,是調用子類重寫過的。

new:子類隱藏父類方法,但,Base B = new Inherited ();B調用方法時,是調用父類本身的方法。

external: 定義在外部;

sealed override: 本子類重寫父類的方法,該方法在繼續繼承的子類裏,不能從新定義,即不能重寫;

properties

特性(區別於attribute),特殊的成員變量,有getset,通常是對類的成員變量的存取封裝,直接使用;

getset有訪問限制,好比 protected set{};表示只能在本類或子類中給屬性賦值;

 

 

2.5 成員函數能夠直接訪問類的全部訪問限制類型的成員變量

訪問時能夠加this,也能夠不加;加this可讀性強,表示操做的是類實例的成員變量,而不是一個局部變量;

        public int Var

        {

            get { return this.nVar; }

            set { this.nVar = value; }

        }

 

2.6 一個問號和兩個問號

變量定義中含有一個問號,意思是這個數據類型是NullAble類型的。

 變量定義中含有兩個問號,意思是取所賦值??左邊的,若是左邊爲null,取所賦值??右邊的。

之長風

 

關於htmlvalue屬性

value屬性是給form標籤的input元素使用。該值設置控件的返回的值。該值不一樣於控件的狀態。好比說,一個checkbox,checked=」checked」,僅僅表示標籤初始爲選中狀態。其攜帶的val值跟該狀態沒有關係,其實做者本身設置使用的,認爲規定的一個值。咱們能夠選擇的時候,設置一個值,未選中的時候設置另外一個值。

 

 

C# 託管資源和非託管資源

https://blog.csdn.net/cai_huan_123/article/details/48269641

 

2015年09月07日 18:01:53

閱讀數:316

託管資源指的是.NET能夠自動進行回收的資源,主要是指託管堆上分配的內存資源。託管資源的回收工做是不須要人工干預的,有.NET運行庫在合適調用垃圾回收器進行回收。

非託管資源指的是.NET不知道如何回收的資源,最多見的一類非託管資源是包裝操做系統資源的對象,例如文件,窗口,網絡鏈接,數據庫鏈接,畫刷,圖標等。這類資源,垃圾回收器在清理的時候會調用Object.Finalize()方法。默認狀況下,方法是空的,對於非託管對象,須要在此方法中編寫回收非託管資源的代碼,以便垃圾回收器正確回收資源。

.NET中,Object.Finalize()方法是沒法重載的,編譯器是根據類的析構函數來自動生成Object.Finalize()方法的,因此對於包含非託管資源的類,能夠將釋放非託管資源的代碼放在析構函數。

注意,不能在析構函數中釋放託管資源,由於析構函數是有垃圾回收器調用的,可能在析構函數調用以前,類包含的託管資源已經被回收了,從而致使沒法預知的結果。

原本若是按照上面作法,非託管資源也可以由垃圾回收器進行回收,可是非託管資源通常是有限的,比較寶貴的,而垃圾回收器是由CRL自動調用的,這樣就沒法保證及時的釋放掉非託管資源,所以定義了一個Dispose()方法,讓使用者可以手動的釋放非託管資源。Dispose()方法釋放類的託管資源和非託管資源,使用者手動調用此方法後,垃圾回收器不會對此類實例再次進行回收。Dispose()方法是由使用者調用的,在調用時,類的託管資源和非託管資源確定都未被回收,因此能夠同時回收兩種資源。

Microsoft爲非託管資源的回收專門定義了一個接口:IDisposable,接口中只包含一個Dispose()方法。任何包含非託管資源的類,都應該繼承此接口。

在一個包含非託管資源的類中,關於資源釋放的標準作法是:

1) 繼承IDisposable接口;

2) 實現Dispose()方法,在其中釋放託管資源和非託管資源,並將對象自己從垃圾回收器中移除(垃圾回收器不在回收此資源);

3) 實現類析構函數,在其中釋放非託管資源。

在使用時,顯示調用Dispose()方法,能夠及時的釋放資源,同時經過移除Finalize()方法的執行,提升了性能;若是沒有顯示調用Dispose()方法,垃圾回收器也能夠經過析構函數來釋放非託管資源,垃圾回收器自己就具備回收託管資源的功能,從而保證資源的正常釋放,只不過由垃圾回收器回收會致使非託管資源的未及時釋放的浪費。

.NET中應該儘量的少用析構函數釋放資源。在沒有析構函數的對象在垃圾處理器一次處理中從內存刪除,但有析構函數的對象,須要兩次,第一次調用析構函數,第二次刪除對象。並且在析構函數中包含大量的釋放資源代碼,會下降垃圾回收器的工做效率,影響性能。因此對於包含非託管資源的對象,最好及時的調用Dispose()方法來回收資源,而不是依賴垃圾回收器。

上面就是.NET中對包含非託管資源的類的資源釋放機制,只要按照上面要求的步驟編寫代碼,類就屬於資源安全的類。

下面用一個例子來總結一下.NET非託管資源回收機制:

 

Public classBaseResource:IDisposable

{

   Private IntPtr handle;// 句柄,屬於非託管資源

   Private Componet comp;// 組件,託管資源

   Private bool isDisposed =false;// 是否已釋放資源的標誌

   

   Public BaseResource

   {

   }

   

   //實現接口方法

   //由類的使用者,在外部顯示調用,釋放類資源

   Public void Dispose()

   {

      Dispose(true);// 釋放託管和非託管資源

   

      //將對象從垃圾回收器鏈表中移除,

      // 從而在垃圾回收器工做時,只釋放託管資源,而不執行此對象的析構函數

      GC.SuppressFinalize(this);

    }

   

   //由垃圾回收器調用,釋放非託管資源

   ~BaseResource()

   {

      Dispose(false);// 釋放非託管資源

   }

   

   //參數爲true表示釋放全部資源,只能由使用者調用

   //參數爲false表示釋放非託管資源,只能由垃圾回收器自動調用

   //若是子類有本身的非託管資源,能夠重載這個函數,添加本身的非託管資源的釋放

   //可是要記住,重載此函數必須保證調用基類的版本,以保證基類的資源正常釋放

   Protected virtualvoid  Dispose(booldisposing)

   {

        If(!this.disposed)// 若是資源未釋放 這個判斷主要用了防止對象被屢次釋放

        {

            If(disposing)

            {

                Comp.Dispose();// 釋放託管資源

            }

   

            closeHandle(handle);// 釋放非託管資源

            handle= IntPtr.Zero;

         }

         this.disposed=true;// 標識此對象已釋放

    }

}

析構函數只能由垃圾回收器調用。

Despose()方法只能由類的使用者調用。

C#中,凡是繼承了IDisposable接口的類,均可以使用using語句,從而在超出做用域後,讓系統自動調用Dispose()方法。 一個資源安全的類,都實現了IDisposable接口和析構函數。提供手動釋放資源和系統自動釋放資源的雙保險

 

 

隱藏和覆蓋

5.1 C++的隱藏和覆蓋

重載、覆蓋和隱藏的區別:

1.重載發生在同一個類中,函數名相同;覆蓋和隱藏發生在有繼承關係的兩個類中。

2.覆蓋和隱藏的區別:

1)若是派生類的函數與基類的函數同名,可是參數不一樣。此時,不論有無virtual
關鍵字,基類的函數將被隱藏(注意別與重載混淆)。
2)若是派生類的函數與基類的函數同名,而且參數也相同,可是基類函數沒有virtual
關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。

在實際應用中應避免出現隱藏。

應用區別:

覆蓋:行爲徹底取決於對象類型的

隱藏:行爲取決於類型的指針

實例:

#include <iostream.h>
class Base
{
public:
virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
void g(float x){ cout << "Base::g(float) " << x << endl; }
void h(float x){ cout << "Base::h(float) " << x << endl; }
};
class Derived : public Base
{
public:
virtual void f(float x){ cout << "Derived::f(float) " << x << endl; }
void g(int x){ cout << "Derived::g(int) " << x << endl; }
void h(float x){ cout << "Derived::h(float) " << x << endl; }
};

void main(void)
{
Derived d;
Base *pb = &d;
Derived *pd = &d;
// Good : behavior depends solely on type of the object
pb->f(3.14f); // Derived::f(float) 3.14
pd->f(3.14f); // Derived::f(float) 3.14
// Bad : behavior depends on type of the pointer
pb->g(3.14f); // Base::g(float) 3.14
pd->g(3.14f); // Derived::g(int) 3 (surprise!)
// Bad : behavior depends on type of the pointer
pb->h(3.14f); // Base::h(float) 3.14 (surprise!)
pd->h(3.14f); // Derived::h(float) 3.14
}

5.2 C#的隱藏和覆蓋

C++相似,virtual是覆蓋,new是隱藏,但new的優先級大於virtual。 即:若是子類方法加了new,不管父類有否virtual都是隱藏。

覆蓋就是,在類的繼承層次上,父類的實現被子類覆蓋掉了。所以若是從子類引用的父類的函數的調用,則調用的是子類的覆蓋後的實現。

綜合:只有一種狀況下是覆蓋,父類加了virtual,子類增長了override的狀況;

 

 

子類有new

子類無new,無override

子類無new,有override

父類virtual

隱藏

隱藏(有告警)

覆蓋

父類無virtual

隱藏

隱藏

隱藏

 

public class MyBaseClass

    {

        public virtual void DoSomething()

        {

            Console.WriteLine("Base imp");

        }

    }

    public class MyDerivedClass : MyBaseClass

    {

        public override void DoSomething()

        {

            Console.WriteLine("Derived imp");

        }

    }

 

    class Program

    {

        static void Main(string[] args)

        {

 

            MyDerivedClass myObj = new MyDerivedClass();

            MyBaseClass myBaseObj;

            myBaseObj = myObj;

            myBaseObj.DoSomething();

 

}

}

 

輸出:Derived imp

 

其餘狀況輸出:Base imp

 

 

 

淺層拷貝與深層拷貝

面嚮對象語言都有深層拷貝與淺層拷貝的問題,主要在於對一個對象進行賦值的時候,就涉及到一個對象的賦值拷貝發生。淺層拷貝,基本上是指直接的成員賦值,這樣若是成員是一個指針(C++),就會兩個不一樣的對象引用同一個內存空間裏,二者就關聯了。對於JAVA語言只有地址引用,所以內建的intstring少許值類型之外,淺層拷貝也會致使兩個對象引用同一處內存的問題。

 

 

6.1 C++的淺層拷貝與深層拷貝

6.1.1 先來了解一下C++中的複製構造函數:

//假設有這樣一個TEST類:

class TEST

{

    private:

        int *num;

    public:

        TEST(int n)

        {

            num = new int;

            *num = n;

        }

 

        void change(int anothernum)

        {

            *num = anothernum;

        }

 

        void print()

        {

            cout << *num << endl;

        }

};

在上述TEST類中並無顯式定義TEST類的複製構造函數, 那麼其默認的複製構造函數應爲:

TEST(const TEST & t)

{

    num = t.num;

}

 

6.1.2 C++的淺層拷貝

以上述TEST類爲例, 假設有以下代碼:

#include<iostream>using namespace std;

class TEST

{

    private:

        int *num;

    public:

        TEST(int n)

        {

            num = new int;

            *num = n;

        }

 

        void change(int anothernum)

        {

            *num = anothernum;

        }

 

        void print()

        {

            cout << *num << endl;

        }

};

int main(void)

{

    TEST a(10);

    TEST b(a);

 

    a.print();

    b.print();

 

    b.change(100);

 

    a.print();

    b.print();

}

上述代碼中並無顯式定義複製構造函數, 運行結果以下:

10

10

100

100

//即更改了b對象的值, a對象的值也隨之改變

上述結果能夠證實, C++的默認構造函數爲淺層複製, 也就是說a對象和b對象指向同一段內存空間.

在小夥伴的提示下, 上述代碼存在一些問題:在TEST類中並無定義析構函數.析構函數的定義應該以下:

~TEST()

{

        delete[] num;

}

那麼定義析構函數和不定義析構函數都會引起什麼樣的問題呢?

  • 定義析構函數
    首先a對象執行析構函數, 釋放掉a對象所指向的空間以後, b對象再執行析構函數, 會釋放掉b對象所指向的空間. 前面提過了, 使用默認的拷貝構造函數拷貝的b對象和a對象應該是指向一樣一段內存空間的. 這樣等於對一段空間delete了兩次, 會形成內存非法訪問, 是極爲不穩當的!!!
  • 不定義析構函數
    若是沒有構造析構函數, 那麼a和b所指向的空間沒法獲得釋放, 這會形成內存泄露, 這樣也是極爲不穩當的!!!

綜上所述, 在TEST類中, 無論是否認義析構函數都是不穩當的, 那這該怎麼辦呢?

  • 拷貝構造函數中涉及到須要動態分配內存的狀況下, 應該自定義拷貝構造函數.
  • 換句話來講, 若是須要自定義析構函數, 那麼也應該自定義拷貝構造函數.

6.1.3 2.C++的深層拷貝

依舊來看一段代碼:

#include<iostream>using namespace std;

class TEST

{

    private:

        int *num;

    public:

        TEST(int n)

        {

            num = new int;

            *num = n;

        }

        //此處顯示定義了TEST類的複製構造函數

        TEST(const TEST & t)

        {

            num = new int;

            *num = *t.num;

        }

 

        void change(int anothernum)

        {

            *num = anothernum;

        }

 

        void print()

        {

            cout << *num << endl;

        }

};

int main(void)

{

    TEST a(10);

    TEST b(a);

 

    a.print();

    b.print();

 

    b.change(100);

 

    a.print();

    b.print();

}

上述代碼中顯式定義了複製構造函數, 自定義的複製構造函數中, a對象在堆上動態申請了空間, 而後再將b對象的值賦值給a對象新申請的這段內存空間. 
運行結果以下:

10

10

10

100

//更改b對象的值並無改變a對象的值, 充分說明a對象和b對象所佔的是不一樣的兩段內存空間

 

6.2 .Java的淺層拷貝和深層拷貝

6.2.1 以數組爲例, 關於Java淺層拷貝和深層拷貝的幾個小知識點

  • 數組複製的兩個方法: 
    System.arraycopy(array1, 0, array2, array1.length);五個參數分別爲:源數組, 源數組起始索引, 目的數組, 源數組長度
    Arrays.copyOf(array1, array1.length);參數分別爲:源數組, 源數組的長度.
  • Java, 只要看到new, 就是創建對象, 即申請一段新的空間. 也就是說, 只要new, 兩者就不是同一個對象,這一點千萬要注意!!!
  • 關於Integer類的一點知識:若使用Integer a = xxx;這樣的形式打包一個值, 要打包的值在Integercache.low~Integercache.high(-128~127)之間, 若在緩存中沒有打包過, 則返回一個new的新對象; 若打包過, 則直接返回打包過的對象; 若不在此範圍內, 則直接返回一個new的新對象. 而使用Integer a = new Integer(xxx);這樣打包出來的值, 必定是一個新的對象.
  • 關於字符串池:Java爲了效率考慮, 凡是以」「寫下的字符串都爲字符串常量, 」「包括的字符串, 只要內容相同, 不管在代碼中出現多少次, JVM都只會創建一個String對象. 也就是說, 「」寫下的字符串都會被放入字符串池中, 日後再出現與」「寫下字符串相同的字符串, 都將直接參考(意大體等同於C中的指向)至字符串池中已有對象, 再也不創建新的對象.

6.2.2 Java的深層拷貝

爲何要先談Java的深層拷貝呢? 
由於在Java, 只要是基本數據類型的數組, 使用上面介紹到的兩個數組拷貝函數進行數組拷貝, 都是深層拷貝! 
來看以下代碼:

public class ArrayCopy {

    public static void main(String args[]){

        int[] array1 = {0, 1, 2, 3, 4, 5};

        int[] array2 = Arrays.copyOf(array1, array1.length);

 

        for(int num : array1){

            System.out.printf("%3d", num);

        }

        System.out.println();

 

        for(int num : array2) {

            System.out.printf("%3d", num);

        }

        System.out.println();

 

        array2[0] = 10;

 

        for(int num : array1){

            System.out.printf("%3d", num);

        }

        System.out.println();

 

        for(int num : array2){

            System.out.printf("%3d", num);

        }

    }

}

由於基本類型的數組的拷貝皆爲深層拷貝額, 因此更改array2數組第一個元素的值, 並不會影響array1數組第一個元素的值. 運行結果以下:

  0  1  2  3  4  5

  0  1  2  3  4  5

  0  1  2  3  4  5

 10  1  2  3  4  5

 

6.2.3 Java的淺層拷貝

不管是使用System.arraycopy()仍是Arrays.copyOf(), 只要用做類類型聲明的數組時, 都執行淺層拷貝, 即源數組與拷貝數組指向同一段內存空間. 
須要特別說明的是, 數組在Java中也是類的對象, 因此二維數組和三維數組在使用System.arraycopy()Arrays.copyOf()的時候, 執行的也是淺層拷貝
關於淺層拷貝就不在這裏舉例子了, 下面來看一看, 如何讓類類型的數組執行深層拷貝.

6.2.4 使類類型的數組執行深層拷貝

看以下代碼:

public class DeepCopy {

    public static class cloths{

        String color;

        char size;

 

        cloths(String col, char si){

            color = col;

            size = si;

        }

    }

 

    public static void main(String[] args){

        cloths[] c1 = {new cloths("red", 'l'), new cloths("blue", 'm')};

        cloths[] c2 = new cloths[c1.length];

 

        for(int i = 0; i < c1.length; i++){

            cloths c = new cloths(c1[i].color, c1[i].size);

            c2[i] = c;

        }

 

        c1[0].color = "yellow";

        System.out.println(c2[0].color);

    }

}

上述代碼, 在複製每個類類型的數組元素時, 都給其new一段新的空間, 使之與源數組元素徹底隔離開. 因此運行結果以下:

red//源數組的第一個元素的color並無被改變

 

6.3 C#的淺層拷貝和深層拷貝

6.3.1 淺層拷貝

C#的淺層拷貝,採用的是System.Object的函數,MemberwiseClone()函數實現的。是一個值拷貝;該函數是一個protected函數,但很容易用一個public成員函數封裝;

下面是一個淺層拷貝的例子:

public class Cloner
{
public int Val;
public Cloner(int newVal)
{
Val = newVal;
}
public object GetCopy()
{
return MemberwiseClone();
}

 

static void main(){

 

Cloner mySource = new Cloner(5);
Cloner myTarget = (Cloner)mySource.GetCopy();
Console.WriteLine("myTarget.MyContent.Val = {0}", myTarget.MyContent.Val);
mySource.MyContent.Val = 2;
Console.WriteLine("myTarget.MyContent.Val = {0}", myTarget.MyContent.Val);

}

 

所以淺層拷貝很適合類中的成員都是值類型;

 

6.3.2 深層拷貝

對於值引用,若是仍然採用淺層拷貝方法,則會致使兩個不一樣的對性共享一個存儲區,引致麻煩。

 

public class Content
{
public int Val;
}


public class Cloner
{
public Content MyContent = new Content();
public Cloner(int newVal)
{
MyContent.Val = newVal;
}
public object GetCopy()
{
return MemberwiseClone();
}
}

 

.NET提供了ICloneable來進行深層拷貝,

public class Content
{
public int Val;
}


public class Cloner : ICloneable
{
public Content MyContent = new Content();
public Cloner(int newVal)
{
MyContent.Val = newVal;
}

public object Clone()
{
Cloner clonedCloner = new Cloner(MyContent.Val);
return clonedCloner;
}
}

 

同時,深層拷貝也支持遞歸拷貝,以下:

public class Cloner : ICloneable
{
public Content MyContent = new Content();
...
public object Clone()
{
Cloner clonedCloner = new Cloner();
clonedCloner.MyContent = MyContent.Clone();
return clonedCloner;
}
}

 

值和引用的轉換(boxing and unboxing)

struct是值引用,class是地址引用;

從值轉換爲引用=boxing

從引用轉爲值=unboxing

 

struct MyStruct
{
public int Val;
}

 

MyStruct valType1 = new MyStruct();
valType1.Val = 5;
object refType = valType1;  //boxing

 

valType1.Val = 6;
MyStruct valType2 = (MyStruct)refType; //unboxing
Console.WriteLine("valType2.Val = {0}", valType2.Val);

 

輸出:valType2.Val = 5

從中能夠看出,boxing的過程並非把地址引用的過程,而是另外拷貝了一個新值。這樣原來的值和boxing產生的新引用直接沒有關係;

 

 

C#中的Attribute

2009年11月01日 19:22:00

閱讀數:627

8.1 區別C#中的兩個屬性(PropertyAttribute)

C#中有兩個屬性,分別爲PropertyAttribute,兩個的中文意思都有特性、屬性之間,可是用法上卻不同,爲了區別,本文暫把Property稱爲特性,把Attribute稱爲屬性。
    Attribute纔是本文的主角,把它稱爲屬性我以爲很恰當。屬性的意思就是附屬於某種事物上的,用來講明這個事物的各類特徵的一種描述。而Attribute就是幹這事的。它容許你將信息與你定義的C#類型相關聯,做爲類型的標註。這些信息是任意的,就是說,它不是由語言自己決定的,你能夠隨意創建和關聯任何類型的任何信息。你能夠做用屬性定義設計時信息和運行時信息,甚至是運行時的行爲特徵。關鍵在於這些信息不只能夠被用戶取出來做爲一種類型的標註,它更能夠被編譯器所識別,做爲編譯時的一種附屬條件參加程序的編譯。
    如下部份內容及代碼來源於《C#技術揭祕》(Inside C# Sencond Edition)

 

8.2 定義屬性

 

屬性其實是一個派生自System.Attribute基類的類。System.Attribute類含有幾個用於訪問和檢查自定義屬性的方法。儘管你有權將任何類定義爲屬性,可是按照慣例來講,從System.Attribute派生類是有意義的。

 

示例以下:
     public enum RegHives
     {
         HKEY_CLASSES_ROOT,
         HKEY_CURRENT_USER,
         HKEY_LOCAL_MACHINE,
         HKEY_USERS,
         HKEY_CURRENT_CONFIG
     }
 
     public class RegKeyAttribute : Attribute
     {
         public RegKeyAttribute(RegHives Hive, String ValueName)
         {
              this.Hive = Hive;
              this.ValueName = ValueName;
         }
 
         protected RegHives hive;
         public RegHives Hive
         {
              get { return hive; }
              set { hive = value; }
         }
 
         protected String valueName;
         public String ValueName
         {
              get { return valueName; }
              set { valueName = value; }
         }
     }
咱們在這裏添加了不一樣註冊表的枚舉、屬性類的構造器以及兩個特性(Property)。在定義屬性時你能夠作許許多多的事情,下面咱們看看如何在運行時查詢屬性。要想在運行時查詢類型或成員所附着的屬性,必須使用反射

 

8.3 查詢屬性

 假設你但願定義一個屬性,這個屬性定義了將在其上建立對象的遠程服務器。若是沒有這個屬性,就要把此信息保存在一個常量中或是一個應用程序的資源文件中。經過使用屬性,只需用如下方法標註出類的遠程服務器名便可:
using System;
 
namespace QueryAttribs
{
     public enum RemoteServers
     {
         JEANVALJEAN,
         JAVERT,
         COSETTE 
     }
 
     public class RemoteObjectAttribute : Attribute
     {
         public RemoteObjectAttribute(RemoteServers Server)
         {
              this.server = Server;
         }
 
         protected RemoteServers server;
         public string Server
         {
              get
              {
                  return RemoteServers.GetName(
                       typeof(RemoteServers), this.server);
              }
         }
     }
 
     [RemoteObject(RemoteServers.COSETTE)]
     class MyRemotableClass
     {
     }
     class Test
     {
         [STAThread]
         static void Main(string[] args)
         {
              Type type = typeof(MyRemotableClass);
              foreach (Attribute attr in
                  type.GetCustomAttributes(true))
              {
                  RemoteObjectAttribute remoteAttr =
                       attr as RemoteObjectAttribute;
                  if (null != remoteAttr)
                  {
                  Console.WriteLine(
                           "Create this object on {0}.",
                           remoteAttr.Server);
                  }
              }
 
              Console.ReadLine();
         }
     }
}
運行結果爲:
Creat this object on COSETTE
注意:在這個例子中的屬性類名具備Attribute後綴。可是,當咱們將此屬性附着給類型或成員時卻不包括Attribute後綴。這是C#語言的設計者提供的簡單方式。當編譯器看到一個屬性被附着給一個類型或成員時,它會搜索具備指定屬性名的System.Attribute派生類。若是編譯器沒有找到匹配的類,它就在指定的屬性名後面加上Attribute,而後再進行搜索。所以,常見的使用作法是將屬性類名定義爲以Attribute結尾,在使用時忽略名稱的這一部分。如下的代碼都採用這種命名方式。


查詢方法屬性


        在下面這個例子中,咱們使用屬性將方法定義爲可事務化的方法,只要存在TransactionableAttribute屬性,代碼就知道具備這個屬性的方法能夠屬於一個事務。
using System;
using System.Reflection;
 
namespace MethodAttribs
{
     public class TransactionableAttribute : Attribute
     {
         public TransactionableAttribute()
         {
         }
     }
 
     class SomeClass
     {
         [Transactionable]
         public void Foo()
         {}
 
         public void Bar()
         {}
 
         [Transactionable]
         public void Goo()
         {}
     }
 
     class Test
     {
         [STAThread]
         static void Main(string[] args)
         {
              Type type = Type.GetType("MethodAttribs.SomeClass");
              foreach (MethodInfo method in type.GetMethods())
              {
                  foreach (Attribute attr in
                       method.GetCustomAttributes(true))
                  {
                       if (attr is TransactionableAttribute)
                       {
                           Console.WriteLine(
                                "{0} is transactionable.",
                                method.Name);
                       }
                  }
              }
 
              Console.ReadLine();
         }
     }
}
運行結果以下:
Foo is transactionable.
Goo is transactionable.
 
查詢字段屬性:
        假設有一個類含有一些字段,咱們但願將它們的值保存進註冊表。爲此,可使用以枚舉值和字符串爲參數的構造器定義一個屬性,這個枚舉值表明正確的註冊表hive,字符串表明註冊表值名稱。在運行時能夠查詢字段的註冊表鍵。
using System;
using System.Reflection;
 
namespace FieldAttribs
{
     public enum RegHives
     {
         HKEY_CLASSES_ROOT,
         HKEY_CURRENT_USER,
         HKEY_LOCAL_MACHINE,
         HKEY_USERS,
         HKEY_CURRENT_CONFIG
     }
 
     public class RegKeyAttribute : Attribute
     {
         public RegKeyAttribute(RegHives Hive, String ValueName)
         {
              this.Hive = Hive;
              this.ValueName = ValueName;
         }
 
         protected RegHives hive;
         public RegHives Hive
         {
              get { return hive; }
              set { hive = value; }
         }
 
         protected String valueName;
         public String ValueName
         {
              get { return valueName; }
              set { valueName = value; }
         }
     }
 
     class SomeClass
     {
         [RegKey(RegHives.HKEY_CURRENT_USER, "Foo")]
         public int Foo;
 
         public int Bar;
     }
 
     class Test
     {
         [STAThread]
         static void Main(string[] args)
         {
              Type type = Type.GetType("FieldAttribs.SomeClass");
              foreach (FieldInfo field in type.GetFields())
              {
                  foreach (Attribute attr in
                       field.GetCustomAttributes(true))
                  {
                       RegKeyAttribute rka =
                           attr as RegKeyAttribute;
                       if (null != rka)
                       {
                           Console.WriteLine(
                                "{0} will be saved in"
                                + " {1}////{2}",
                                field.Name,
                                rka.Hive,
                                rka.ValueName);
                       }
                  }
              }
 
              Console.ReadLine();
         }
     }
}
運行結果爲:
Foo will be saved in HKEY_CURRENT_USER//Foo
 
        你們能夠看到,用屬性來標註類、方法、字段,既能夠把用戶的自定義信息附屬在實體上,又能夠在運行時動態的查詢。下面我將講一些C#中默認的預約義屬性,見下表:
預約義的屬性 有效目標 說明 
AttributeUsage Class 指定另外一個屬性類的有效使用方式 
CLSCompliant 所有 指出程序元素是否與CLS兼容 
Conditional Method 指出若是沒有定義相關聯的字符串,編譯器就能夠忽略對這個方法的任何調用 
DllImport Method 指定包含外部方法的實現的DLL位置 
STAThread Method(Main) 指出程序的默認線程模型爲STA 
MTAThread Method(Main) 指出程序的默認模型爲多線程(MTA) 
Obsolete 除了AssemblyModuleParameterReturn 將一個元素標示爲不可用,通知用戶此元素將被從將來的產品 
ParamArray Parameter 容許單個參數被隱式地看成params(數組)參數對待 
Serializable ClassStructenumdelegate 指定這種類型的全部公共和私有字段能夠被串行化 
NonSerialized Field 應用於被標示爲可串行化的類的字段,指出這些字段將不可被串行化 
StructLayout Classstruct 指定類或結構的數據佈局的性質,好比AutoExplicitsequential 
ThreadStatic Field(靜態) 實現線程局部存儲(TLS)。不能跨多個線程共享給定的靜態字段,每一個線程擁有這個靜態字段的副本

 
下面介紹幾種經常使用的屬性
1.[STAThread][MTAThread]屬性
class Class1
{
        [STAThread]
        Static void Main( string[] args )
        {
        }
}
使用STAThread屬性將程序的默認線程模型指定爲單線程模型。注意,線程模型隻影響使用COM interop的應用程序,將這個屬性應用於不使用COM interop的程序將不會產生任何效果。


2. AttributeUsage屬性
        除了用於標註常規C#類型的自定義屬性之外,還可使用AttributeUsage屬性定義你使用這些屬性的方式。文件記錄的AttributeUsage屬性調用用法以下:
[AttributeUsage( validon , AllowMutiple = allowmutiple , Inherited = inherited )]
Validon參數是AttributeTargets類型的,這個枚舉值的定義以下:
public enum AttributeTargets
{
        Assembly = 0x0001,
        Module = 0x0002,
        Class = 0x0004,
        Struct = 0x0008,
        Enum = 0x0010,
        Constructor = 0x0020,
        Method = 0x0040,
        Property = 0x0080,
        Field = 0x0100,
        Event = 0x200,
        Interface = 0x400,
        Parameter = 0x800,
        Delegate = 0x1000,
        All = Assembly | Module | Class | Struct | Enum | Constructor| Method | Property|                    Filed| Event| Interface | Parameter | Deleagte ,
        ClassMembers = | Class | Struct | Enum | Constructor | Method | Property | Field |                    Event | Delegate | Interface 
}
AllowMultiple決定了能夠在單個字段上使用某個屬性多少次,在默認狀況下,全部的屬性都是單次使用的。示例以下:
[AttributeUsage( AttributeTargets.All , AllowMultiple = true )]
public class SomethingAttribute : Attribute
{
        public SomethingAttribute( string str )
        {
        }
}
//若是AllowMultiple = false , 此處會報錯
[Something(「abc」)]
[Something(「def」)]
class Myclass
{
}
Inherited參數是繼承的標誌,它指出屬性是否能夠被繼承。默認是false
Inherited AllowMultiple 結果 
true false 派生的屬性覆蓋基屬性 
true false 派生的屬性和基屬性共存

代碼示例:
using System;
using System.Reflection;
 
namespace AttribInheritance
{
     [AttributeUsage(
          AttributeTargets.All,
          AllowMultiple=true,
//       AllowMultiple=false,
          Inherited=true
     )]
     public class SomethingAttribute : Attribute
     {
         private string name;
         public string Name
         {
              get { return name; }
              set { name = value; }
         }
 
         public SomethingAttribute(string str)
         {
              this.name = str;
         }
     }
         
     [Something("abc")]
     class MyClass
     {
     }
 
     [Something("def")]
     class Another : MyClass
     {
     }
         
     class Test
     {
         [STAThread]
         static void Main(string[] args)
         {
              Type type =
                  Type.GetType("AttribInheritance.Another");
              foreach (Attribute attr in
                  type.GetCustomAttributes(true))
//                type.GetCustomAttributes(false))
              {
                  SomethingAttribute sa =
                       attr as SomethingAttribute;
                  if (null != sa)
                  {
                  Console.WriteLine(
                           "Custom Attribute: {0}",
                           sa.Name);
                  }
              }
 
         }
     }
}
AllowMultiple被設置爲false時,結果爲:
Custom Attribute : def
AllowMultiple被設置爲true時,結果爲:
Custom Attribute : def
Custom Attribute : abc
注意,若是將false傳遞給GetCustomAttributes,它不會搜索繼承樹,因此你只能獲得派生的類屬性。
 
3.Conditional 屬性
        你能夠將這個屬性附着於方法,這樣當編譯器遇到對這個方法調用時,若是沒有定義對應的字符串值,編譯器就忽略這個調用。例如,如下方法是否被編譯取決因而否認義了字符串「DEGUG」:
[Condition(「DEBUG」) ]
public void SomeDebugFunc()
{
        Console.WriteLine(「SomeDebugFunc」);
}
using System;
using System.Diagnostics;
 
namespace CondAttrib
{
     class Thing
     {
         private string name;
         public Thing(string name)
         {
              this.name = name;
              #if DEBUG
                  SomeDebugFunc();
              #else
                  SomeFunc();
              #endif
         }
         public void SomeFunc()
              { Console.WriteLine("SomeFunc"); }
 
         [Conditional("DEBUG")]
         [Conditional("ANDREW")]
         public void SomeDebugFunc()
              { Console.WriteLine("SomeDebugFunc"); }
     }
 
     public class Class1
     {
         [STAThread]
         static void Main(string[] args)
         {
              Thing t = new Thing("T1");
         }
     }
}
 
4. Obsolete 屬性
        隨着代碼不斷的發展,你很能夠會有一些方法不用。能夠將它們都刪除,可是有時給它們加上適當的標註比刪除它們更合適,例如:
using System;
 
namespace ObsAttrib
{
     class SomeClass
     {
         [Obsolete("Don't use OldFunc, use NewFunc instead", true)]
         public void OldFunc( ) { Console.WriteLine("Oops"); }
 
         public void NewFunc( ) { Console.WriteLine("Cool"); }
     }
 
     class Class1
     {
         [STAThread]
         static void Main(string[] args)
         {
              SomeClass sc = new SomeClass();
              sc.NewFunc();
//            sc.OldFunc();     // compiler error
         }
     }
}
咱們將Obsolete屬性的第二個參數設置爲true,當調用時函數時編譯器會產生一個錯誤。
E:/InsideC#/Code/Chap06/ObsAttrib/ObsAttrib/Class1.cs(20): 'ObsAttrib.SomeClass.OldFunc()' 已過期: 'Don't use OldFunc, use NewFunc instead'
 
5. DllImportStructLayout屬性
        DllImport可讓C#代碼調用本機代碼中的函數,C#代碼經過平臺調用(platform invoke)這個運行時功能調用它們。
        若是你但願運行時環境將結構從託管代碼正確地編組現非託管代碼(或相反),那麼須要爲結構的聲明附加屬性。爲了使結構參數能夠被正確的編組,必須使用StructLayout屬性聲明它們,指出數據應該嚴格地按照聲明中列出的樣子進行佈局。若是不這麼作,數據將不能正確地被編組,而應用程序可能會出錯。
using System;
using System.Runtime.InteropServices;    // for DllImport
 
namespace nativeDLL
{
     public class Test
     {
//       [DllImport ("user32.dll")]           // all the defaults are OK
         [DllImport("user32", EntryPoint="MessageBoxA",
              SetLastError=true,
              CharSet=CharSet.Ansi, ExactSpelling=true,
              CallingConvention=CallingConvention.StdCall)]
         public static extern int MessageBoxA (
              int h, string m, string c, int type);
 
         [StructLayout(LayoutKind.Sequential)]
         public class SystemTime {
              public ushort wYear;
              public ushort wMonth;
              public ushort wDayOfWeek;
              public ushort wDay;
              public ushort wHour;
              public ushort wMinute;
              public ushort wSecond;
              public ushort wMilliseconds;
         }
 
         [DllImport ("kernel32.dll")]
         public static extern void GetLocalTime(SystemTime st);
 
         [STAThread]
         public static void Main(string[] args)
         {
              MessageBoxA(0, "Hello World", "nativeDLL", 0);
 
              SystemTime st = new SystemTime();
              GetLocalTime(st);
              string s = String.Format("date: {0}-{1}-{2}",
                  st.wMonth, st.wDay, st.wYear);
              string t = String.Format("time: {0}:{1}:{2}",
                  st.wHour, st.wMinute, st.wSecond);
              string u = s + ", " + t;
 
              MessageBoxA(0, u, "Now", 0);
         }
     }
}
 
6. 配件屬性
        當使用.NET產生任何類型的C#工程時,會自動的產生一個AssemblyInfo.cs源代碼文件以及應用程序源代碼文件。AssemblyInfo.cs中含有配件中代碼的信息。其中的一些信息純粹是信息,而其它信息使運行時環境能夠確保唯一的命名和版本號,以供重用你的配件的客戶代碼使用。
 
7. 上下文屬性
        .NET櫃架還提供了另外一種屬性:上下文屬性。上下文屬性提供了一種截取機制,能夠在類的實例化和方法調用以前和以後進行處理。這種功能用於對象遠程調用,它是從基於COM的系統所用的COM+組件服務和Microsoft Transaction Services(MTS)

 

C#string.format用法詳解

9.1 String.Format 方法的幾種定義:

String.Format (String, Object) 將指定的 String 中的格式項替換爲指定的 Object 實例的值的文本等效項。
String.Format (String, Object[]) 將指定 String 中的格式項替換爲指定數組中相應 Object 實例的值的文本等效項。
String.Format (IFormatProvider, String, Object[]) 將指定 String 中的格式項替換爲指定數組中相應 Object 實例的值的文本等效項。指定的參數提供區域性特定的格式設置信息。
String.Format (String, Object, Object) 將指定的 String 中的格式項替換爲兩個指定的 Object 實例的值的文本等效項。 
String.Format (String, Object, Object, Object) 將指定的 String 中的格式項替換爲三個指定的 Object 實例的值的文本等效項。
 

9.2 經常使用的格式化數值結果表
 

字符

說明

示例

輸出

C

貨幣

string.Format("{0:C3}", 2)

$2.000

D

十進制

string.Format("{0:D3}", 2)

002

E

科學計數法

1.20E+001

1.20E+001

G

常規

string.Format("{0:G}", 2)

2

N

用分號隔開的數字

string.Format("{0:N}", 250000)

250,000.00

X

十六進制

string.Format("{0:X000}", 12)

C

 

 

string.Format("{0:000.000}", 12.2)

012.200


經常使用的幾種實例

9.3 字符串的數字格式

複製代碼 代碼以下:

string str1 =string.Format("{0:N1}",56789);               //result: 56,789.0
 string str2 =string.Format("{0:N2}",56789);               //result: 56,789.00
 string str3 =string.Format("{0:N3}",56789);               //result: 56,789.000
 string str8 =string.Format("{0:F1}",56789);               //result: 56789.0
 string str9 =string.Format("{0:F2}",56789);               //result: 56789.00
 string str11 =(56789 / 100.0).ToString("#.##");           //result: 567.89
 string str12 =(56789 / 100).ToString("#.##");             //result: 567

 

9.4 格式化貨幣(跟系統的環境有關,中文系統默認格式化人民幣,英文系統格式化美圓)

複製代碼 代碼以下:

string.Format("{0:C}",0.2)

結果爲:¥0.20 (英文操做系統結果:$0.20)
默認格式化小數點後面保留兩位小數,若是須要保留一位或者更多,能夠指定位數

複製代碼 代碼以下:

string.Format("{0:C1}",23.15)

結果爲:¥23.2 (截取會自動四捨五入)
格式化多個Object實例

複製代碼 代碼以下:

string.Format("市場價:{0:C},優惠價{1:C}",23.15,19.82)

 

9.5 格式化十進制的數字(格式化成固定的位數,位數不能少於未格式化前,只支持整形)

複製代碼 代碼以下:

string.Format("{0:D3}",23) //結果爲:023
string.Format("{0:D2}",1223) //結果爲:1223,(精度說明符指示結果字符串中所需的最少數字個數。)

 

9.6 用分號隔開的數字,並指定小數點後的位數

複製代碼 代碼以下:

string.Format("{0:N}", 14200) //結果爲:14,200.00 (默認爲小數點後面兩位)
string.Format("{0:N3}", 14200.2458) //結果爲:14,200.246 (自動四捨五入)
 

9.7 格式化百分比

string.Format("{0:P}", 0.24583) //結果爲:24.58% (默認保留百分的兩位小數)

string.Format("{0:P1}", 0.24583) //結果爲:24.6% (自動四捨五入)

 

9.8 零佔位符和數字佔位符

複製代碼 代碼以下:

string.Format("{0:0000.00}", 12394.039) //結果爲:12394.04
string.Format("{0:0000.00}", 194.039) //結果爲:0194.04
string.Format("{0:###.##}", 12394.039) //結果爲:12394.04
string.Format("{0:####.#}", 194.039) //結果爲:194


 下面的這段說明比較難理解,多測試一下實際的應用就能夠明白了。
零佔位符: 若是格式化的值在格式字符串中出現「0」的位置有一個數字,則此數字被複制到結果字符串中。小數點前最左邊的「0」的位置和小數點後最右邊的「0」的位置肯定總在結果字符串中出現的數字範圍。 「00」說明符使得值被舍入到小數點前最近的數字,其中零位總被捨去。
數字佔位符: 若是格式化的值在格式字符串中出現「#」的位置有一個數字,則此數字被複制到結果字符串中。不然,結果字符串中的此位置不存儲任何值。
請注意,若是「0」不是有效數字,此說明符永不顯示「0」字符,即便「0」是字符串中惟一的數字。若是「0」是所顯示的數字中的有效數字,則顯示「0」字符。 「##」格式字符串使得值被舍入到小數點前最近的數字,其中零總被捨去。

 

9.9 日期格式化

複製代碼 代碼以下:

string.Format("{0:d}",System.DateTime.Now) //結果爲:2009-3-20 (月份位置不是03)
string.Format("{0:D}",System.DateTime.Now) //結果爲:2009年3月20日
string.Format("{0:f}",System.DateTime.Now) //結果爲:2009年3月20日 15:37
string.Format("{0:F}",System.DateTime.Now) //結果爲:2009年3月20日 15:37:52
string.Format("{0:g}",System.DateTime.Now) //結果爲:2009-3-20 15:38
string.Format("{0:G}",System.DateTime.Now) //結果爲:2009-3-20 15:39:27
string.Format("{0:m}",System.DateTime.Now) //結果爲:3月20日
string.Format("{0:t}",System.DateTime.Now) //結果爲:15:41
string.Format("{0:T}",System.DateTime.Now) //結果爲:15:41:50

 

 

 

10 MVCEF版本對應關係

11 Asp.Net MVC EF各版本區別

原文:https://www.cnblogs.com/freeliver54/p/6380719.html

2009年發行ASP.NET MVC 1.0

2010年發行ASP.NET MVC 2.0版,VS2010

2011年發行ASP.NET MVC 3.0+EF4,須要.Net4.0支持,VS2011

2012年發行ASP.NET MVC 4.0+EF5,須要.Net4.0支持,VS2012

2013年發行ASP.NET MVC 5.0+EF6,須要.Net4.5支持,VS2013

2015年發行ASP.NET MVC 6.0+EF7,須要.Net5.0支持,VS2015

ASP.NET 5.0      將更名爲  ASP.NET Core 1.0

ASP.NET MVC 6     將更名爲  ASP.NET MVC Core 1.0

Entity Framework 7.0       將更名爲  Entity Framework Core 1.0

.NET新的跨平臺版本將命名爲.NET Core 1.0

 

12 個人控制反轉,依賴注入和麪向切面編程的理解

https://blog.csdn.net/PacosonSWJTU/article/details/52786216

 

1.什麼是控制? 以下圖所示,咱們看到了 軟件系統中 對象的 高耦合現象。全體齒輪的轉動由一個對象來控制,如類B。

 

 

 

 

2.什麼是 控制反轉? 是用來對對象進行解耦藉助第三方實現具備依賴關係的的對象之間的解耦。這個第三方就是 ioc 容器。引入了 ioc 容器後,對象 A、B、C、D 之間沒有了依賴關係全體齒輪轉動的控制權交給 容器。這時候齒輪轉動控制權不屬於任何對象,而屬於ioc 容器,因此控制權反轉了,從 某個對象 轉到了 ioc 容器。

 

 

3. 什麼是依賴注入?

3.1 什麼是依賴?依賴就是指一種關係,若是 在類A中 建立了 類B的實例,咱們說 類A 依賴 類B。

3.2 看個荔枝:

public class class A{

B b;

public A(){

b = new B();

}

void func(){

b.func();

}

}

出現的問題(problems):

  • 問題1:若是如今要改變 B 生成方式,如須要用new B(String name)初始化 B,須要修改 類A中的源代碼;
  • 問題2:若是想測試不一樣 B 對象對 A 的影響很困難,由於 B 的初始化被寫死在了 A 的構造函數中
  • 問題3:若是要對類B的實例進行調試時,就必須在類A中對類B的實例進行測試,增長了測試難度和複雜度;由於當出現問題時,不知道 是 類A的問題 仍是 類B的問題;

解決方法:

public class class A{

B b;

public A(B b){

this.b = b;

}

void func(){

b.func();

}

}

3.3)依賴注入定義: B對象實例 做爲類A的構造器參數進行傳入,在調用類A 構造器以前,類B實例已經被初始化好了。像這種非本身主動初始化依賴,而經過外部傳入依賴對象的方式,咱們就稱爲依賴注入

 

下面是工廠方法實現依賴注入 
當咱們調用的時候直接講用工廠方法,讓工廠方法去new出對象,與咱們脫離關係

Class C {

    J j ;

    Human h = new Human;

    j=Human.getJ();

}

 

4.依賴反轉

4.1 根據依賴注入的定義: 被依賴者對象並非依賴者本身主動初始化,而是經過外部傳入被依賴者的方式,那麼被依賴者對象類型 能夠是 自己,也可使其實現類或繼承子類;

4.2 因此,通過分析,被依賴者的對象類型 並非 依賴者自身能夠決定的,(固然傳統的程序設計方式是依賴者 決定的),而是由外部建立者決定的,因此 被依賴者類型的決定權反轉了。對於spirng來講 ,就是由 spring容器決定的;

 

4.3 依賴反轉定義:被依賴者的 對象類型 並非由依賴者自身能夠決定的,而是由 外部建立者決定的,外部傳入什麼類型的對象 就是 什麼類型的對象 依賴者對其一無所知;

 

=======================================================================================

AOP】

轉自:http://docs.jboss.org/aop/1.0/aspect-framework/userguide/en/html/what.html

1】What is it? 面向切面編程是什麼?

1)切面定義:一個切面是一般指散佈在方法,類,對象層次結構或甚至整個對象模型中的共同特徵 它是看起來和睦味的行爲差很少,它應該有結構,但你不能用代碼在傳統的面向對象技術中表示這種結構。

2)切面荔枝:例如,度量(時間,用於度量運行某個代碼片須要花費多長時間)是一個常見切面。 要從應用程序生成有用的日誌,您必須(一般自由地)在代碼中散佈信息性消息。 然而,度量是你的類或對象模型真正不該該關注的東西。 畢竟,度量與您的實際應用無關:它不表明客戶或賬戶,而且不實現業務規則。 它只是正交。

3)橫切關注點定義:AOP中,像度量這樣的特徵稱爲橫切關注點,由於它是一種「截斷」對象模型中多個點但仍然大相徑庭的行爲。做爲一種開發方法,AOP建議您抽象和封裝橫切關注點。

4)橫切關注點荔枝:例如,假設您想要嚮應用程序添加代碼以測量調用特定方法所需的時間。 在純Java中,代碼看起來像下面這樣。

public class BankAccountDAO {

  public void withdraw(double amount)  {

    long startTime = System.currentTimeMillis();

    try {

      // Actual method body...

    }

    finally {

      long endTime = System.currentTimeMillis() - startTime;

      System.out.println("withdraw took: " + endTime);

    }

  }

}

代碼分析)雖然這段代碼工做,這個方法有一些問題:

  • 打開和關閉指標是很是困難的,由於您必須手動將try> / finally塊中的代碼添加到要基準的每一個方法或構造函數。
  • 分析代碼真的不屬於你的應用程序代碼。 它使你的代碼膨脹和更難讀,由於你必須在一個try / finally塊中包含時間。
  • 若是要擴展此功能以包括方法或失敗計數,或者甚至將這些統計信息註冊到更復雜的報告機制,則必須再次修改許多不一樣的文件。

這種度量方法很難維護,擴展和擴展,由於它分散在整個代碼庫中。 這只是一個很小的例子! 在許多狀況下,OOP可能不老是向類添加指標的最佳方法。

面向方面的編程提供了一種封裝這種類型的行爲功能的方法。 它容許您添加行爲,如度量「圍繞」您的代碼。 例如,AOP爲您提供了程序控制,以指定您但願在執行代碼的實際主體以前調用BankAccountDAO來執行度量方面。

 

2】在JBoss 切面編程中建立切面

1)簡而言之,全部AOP框架定義了兩個東西:一種實現橫切關注點的方法,以及一個編程語言或一組標籤 -以指定如何應用這些代碼片斷。(一種是 定義橫切關注點的方法,二是指定該橫切關注點在何處使用)

2)讓咱們來看看JBoss AOP,它的橫切關注點,以及如何在JBoss中實現一個度量方面。

JBoss AOP中建立度量方面的第一步是將度量特性封裝在本身的Java類中。 清單二將清單One的BankAccountDAO.withdraw()方法中的try / finally塊提取爲Metrics,這是一個JBoss AOP攔截器類的實現。

清單二:在JBoss AOP攔截器中實現度量

public class Metrics implements org.jboss.aop.Interceptor

03.   public Object invoke(Invocation invocation) throws Throwable {

05.     long startTime = System.currentTimeMillis();

06.     try {

08.       return invocation.invokeNext();

09.     }

10.     finally {

12.       long endTime = System.currentTimeMillis() - startTime;

13.       java.lang.reflect.Method m = ((MethodInvocation)invocation).method;

14.       System.out.println("method " + m.toString() + " time: " + endTime + "ms");

15.     }

16.   }

17. }

對以上代碼的分析:

  • JBoss AOP下,Metrics類包裝withdraw():當調用代碼調用withdraw()時,AOP框架將方法調用分解爲其部分,並將這些部分封裝到一個調用對象中。 而後框架調用位於調用代碼和實際方法體之間的任何方面。
  • AOP框架解析方法調用時,它在第3行調用Metric的invoke方法。第8行包裝並委託給實際的方法,並使用一個封閉的try / finally塊來執行定時。 第13行從調用對象獲取有關方法調用的上下文信息,而第14行顯示方法名稱和計算的度量。
  • 將度量代碼放在其本身的對象中容許咱們之後輕鬆擴展和捕獲額外的測量。如今,度量被封裝到一個方面,讓咱們看看如何應用它。

3】JBoss AOP中切面的應用

要應用一個方面,您定義什麼時候執行方面代碼。 這些執行點被稱爲切入點。 相似於切入點是一個正則表達式。 正則表達式匹配字符串時,切入點表達式匹配應用程序中的事件/點。 例如,有效的切入點定義將是「對於JDBC方法executeQuery()的全部調用,調用驗證SQL語法的方面」。

 

個人總結(AOP)

1)切面的抽象型定義:切面是散佈在 方法,類,對象層次結構或 整個對象模型中的共同特徵;(是一組特徵)

2)切面荔枝: 咱們要對某方法的執行時間進行統計,代碼以下:

public class BankAccountDAO {

  public void withdraw(double amount)  {

    long startTime = System.currentTimeMillis();

    try {

      // Actual method body...

    }

    finally {

      long endTime = System.currentTimeMillis() - startTime;

      System.out.println("withdraw took: " + endTime);

    }

  }

}

其中 有關於統計時間的都是特徵,它們的共性都是由於要統計時間而 添加到 方法中的代碼塊;那因此這些代碼塊就組成了 切面;

3)以上代碼塊出現了問題

問題1)代碼重用率低:必須手動將該 切面代碼添加到 要統計方法執行時間的方法中;

問題2)代碼膨脹和可讀性低:計時代碼真的不用你去關心,這些切面代碼只會讓你的代碼更加 膨脹和難讀,由於你必須在一個try / finally塊中包含時間, 但這個時間與咱們 方法的執行沒有任何關係;

問題3)不利於代碼擴展(代碼耦合性高):若是要擴展此功能以包括方法或失敗計數,或者甚至將這些統計信息註冊到更復雜的報告機制,則必須再次修改許多不一樣的文件;

最後的最後:這種時間度量方法很難維護,擴展和擴展,由於它分散在整個代碼庫中;

4)aop 是幹什麼的?

4.1)intro:面向切面編程提供了一種封裝切面行爲的方法。AOP提供了程序控制,以指定您但願在執行代碼的實際主體以前調用 時間統計方法 來執行執行時間度量。

4.2)全部AOP框架定義了兩個東西:一種實現橫切關注點的方法,以及一個編程語言或一組標籤 -以指定如何應用這些代碼片斷。(一種是 定義橫切關注點的方法,二是指定該橫切關注點在什麼時候何處使用)

 

5)如何定義aop中橫切關注點的方法 和 指定該橫切關注點的應用地點

5.1)定義橫切關注點:將切面代碼度量特性封裝在本身的Java類中

public class Metrics implements org.jboss.aop.Interceptor

   public Object invoke(Invocation invocation) throws Throwable {

     long startTime = System.currentTimeMillis();

     try {

       return invocation.invokeNext(); // 調用真正的實體方法

     }

     finally {

       long endTime = System.currentTimeMillis() - startTime;

       java.lang.reflect.Method m = ((MethodInvocation)invocation).method;

       System.out.println("method " + m.toString() + " time: " + endTime + "ms");

     }

   }

 }

5.2)橫切關注點在什麼時候何地使用?

要應用一個切面,您定義什麼時候執行切面代碼。 這些執行點被稱爲切入點。切入點相似因而一個正則表達式。 正則表達式匹配字符串時,切入點表達式匹配應用程序中的事件/點。

看個 切入點的定義: @Before("execution(** concert.Performance.perform(..))")

 

 

13 ASP.NET MVC - 依賴注入(DI)和Ninject

https://blog.csdn.net/nic7968/article/details/14162523

本文目錄:

13.1 爲何須要依賴注入

[ASP.NET MVC 小牛之路]系列的理解MVC模式文章中,咱們提到MVC的一個重要特徵是關注點分離(separation of concerns)。咱們但願應用程序的各部分組件儘量多的相互獨立、儘量少的相互依賴。

咱們的理想狀況是:一個組件能夠不知道也能夠不關心其餘的組件,但經過提供的公開接口卻能夠實現其餘組件的功能調用。這種狀況就是所謂的鬆耦合

舉個簡單的例子。咱們要爲商品定製一個「高級」的價錢計算器LinqValueCalculator,這個計算器須要實現IValueCalculator接口。以下代碼所示:

 

public interface IValueCalculator {

    decimal ValueProducts(params Product[] products);

}

public class LinqValueCalculator : IValueCalculator {

    public decimal ValueProducts(params Product[] products) {

        return products.Sum(p => p.Price);

    }

}

 

Product類和前兩篇博文中用到的是同樣的。如今有個購物車ShoppingCart類,它須要有一個能計算購物車內商品總價錢的功能。但購物車自己沒有計算的功能,所以,購物車要嵌入一個計算器組件,這個計算器組件能夠是LinqValueCalculator組件,但不必定是LinqValueCalculator組件(之後購物車升級,可能會嵌入別的更高級的計算器)。那麼咱們能夠這樣定義購物車ShoppingCart類:

 

 1 public class ShoppingCart { 2     //計算購物車內商品總價錢 3     public decimal CalculateStockValue() { 4         Product[] products = {  5             new Product {Name = "西瓜", Category = "水果", Price = 2.3M},  6             new Product {Name = "蘋果", Category = "水果", Price = 4.9M},  7             new Product {Name = "空心菜", Category = "蔬菜", Price = 2.2M},  8             new Product {Name = "地瓜", Category = "蔬菜", Price = 1.9M}  9         };10         IValueCalculator calculator = new LinqValueCalculator();11 12         //計算商品總價錢 13         decimal totalValue = calculator.ValueProducts(products);14 15         return totalValue;16     }17 }

 

ShoppingCart類是經過IValueCalculator接口(而不是經過LinqValueCalculator)來計算商品總價錢的。若是之後購物車升級須要使用更高級的計算器,那麼只須要改變第10行代碼中new後面的對象(即把LinqValueCalculator換掉),其餘的代碼都不用變更。這樣就實現了必定的鬆耦合。這時三者的關係以下圖所示:

 

這個圖說明,ShoppingCart類既依賴IValueCalculator接口又依賴LinqValueCalculator類。這樣就有個問題,用現實世界的話來說就是,若是嵌入在購物車內的計算器組件壞了,會致使整個購物車不能正常工做,豈不是要把整個購物車要換掉!最好的辦法是將計算器組件和購物車徹底獨立開來,這樣無論哪一個組件壞了,只要換對應的組件便可。即咱們要解決的問題是,要讓ShoppingCart組件和LinqValueCalculator組件徹底斷開關係,而依賴注入這種設計模式就是爲了解決這種問題。

13.2 什麼是依賴注入

上面實現的部分鬆耦合顯然並非咱們所須要的。咱們所須要的是,在一個類內部,不經過建立對象的實例而可以得到某個實現了公開接口的對象的引用。這種「須要」,就稱爲DI(依賴注入,Dependency Injection),和所謂的IoC(控制反轉,Inversion of Control )是一個意思。

DI是一種經過接口實現鬆耦合的設計模式。初學者可能會好奇網上爲何有那麼多技術文章對DI這個東西大興其筆,是由於DI對於基於幾乎全部框架下,要高效開發應用程序,它都是開發者必需要有的一個重要的理念,包括MVC開發。它是解耦的一個重要手段。

DI模式可分爲兩個部分。一是移除對組件(上面示例中的LinqValueCalculator)的依賴,二是經過類的構造函數(或類的Setter訪問器)來傳遞實現了公開接口的組件的引用。以下面代碼所示:

 

public class ShoppingCart {

    IValueCalculator calculator;

    

    //構造函數,參數爲實現了IEmailSender接口的類的實例

    public ShoppingCart(IValueCalculator calcParam) {

        calculator = calcParam;

    }

 

    //計算購物車內商品總價錢

    public decimal CalculateStockValue() {

        Product[] products = {

            new Product {Name = "西瓜", Category = "水果", Price = 2.3M},

            new Product {Name = "蘋果", Category = "水果", Price = 4.9M},

            new Product {Name = "空心菜", Category = "蔬菜", Price = 2.2M},

            new Product {Name = "地瓜", Category = "蔬菜", Price = 1.9M}

        };

 

        //計算商品總價錢

        decimal totalValue = calculator.ValueProducts(products);

 

        return totalValue;

    }

}

 

這樣咱們就完全斷開了ShoppingCart和LinqValueCalculator之間的依賴關係。某個實現了IValueCalculator接口的類(示例中的MyEmailSender)的實例引用做爲參數,傳遞給ShoppingCart類的構造函數。可是ShoppingCart類不知道也不關心這個實現了IValueCalculator接口的類是什麼,更沒有責任去操做這個類。 這時咱們能夠用下圖來描述ShoppingCart、LinqValueCalculator和IValueCalculator之間的關係:

 

在程序運行的時候,依賴被注入到ShoppingCart,這個依賴就是,經過ShoppingCart構造函數傳遞實現了IValueCalculator接口的類的實例引用。在程序運行以前(或編譯時),ShoppingCart和任何實現IValueCalculator接口的類沒有任何依賴關係。(注意,程序運行時是有具體依賴關係的。)

注意,上面示例使用的注入方式稱爲「構造注入」,咱們也能夠經過屬性來實現注入,這種注入被稱爲「setter 注入」,就不舉例了,朋友們能夠看看T2噬菌體的文章依賴注入那些事兒來對DI進行更多的瞭解。

因爲常常會在編程時使用到DI,因此出現了一些DI的輔助工具(或叫DI容器),如Unity和Ninject等。因爲Ninject的輕量和用簡單,加上本人只用過Ninject,因此本系列文章選擇用它來開發MVC應用程序。下面開始介紹Ninject,但在這以前,先來介紹一個安裝Ninject須要用到的插件-NuGet。

13.3 使用NuGet安裝庫

NuGet 是一種 Visual Studio 擴展,它可以簡化在 Visual Studio 項目中添加、更新和刪除庫(部署爲程序包)的操做。好比你要在項目中使用Log4Net這個庫,若是沒有NuGet這個擴展,你可能要先到網上搜索Log4Net,再將程序包的內容解壓縮到解決方案中的特定位置,而後在各項目工程中依次添加程序集引用,最後還要使用正確的設置更新 web.config。而NuGet能夠簡化這一切操做。例如咱們在講依賴注入的項目中,若要使用一個NuGet庫,可直接右擊項目(或引用),選擇「管理NuGet程序包」(VS2010下爲「Add Library Package Reference」),以下圖:

 

在彈出以下窗口中選擇「聯機」,搜索「Ninject」,而後進行相應的操做便可:

 

在本文中咱們只須要知道如何使用NuGet來安裝庫就能夠了。NuGet的詳細使用方法可查看MSDN文檔:使用 NuGet 管理項目庫

13.4 使用Ninject的通常步驟

在使用Ninject前先要建立一個Ninject內核對象,代碼以下:

class Program {

    static void Main(string[] args) {

        //建立Ninject內核實例

        IKernel ninjectKernel = new StandardKernel();

    }

}

使用Ninject內核對象通常可分爲兩個步驟。第一步是把一個接口(IValueCalculator)綁定到一個實現該接口的類(LinqValueCalculator),以下:

...//綁定接口到實現了該接口的類

ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator<();

...

這個綁定操做就是告訴Ninject,當接收到一個請求IValueCalculator接口的實現時,就返回一個LinqValueCalculator類的實例。

第二步是用Ninject的Get方法去獲取IValueCalculator接口的實現。這一步,Ninject將自動爲咱們建立LinqValueCalculator類的實例,並返回該實例的引用。而後咱們能夠把這個引用經過構造函數注入到ShoppingCart類。以下代碼所示:

 

...// 得到實現接口的對象實例

IValueCalculator calcImpl = ninjectKernel.Get<IValueCalculator>(); // 建立ShoppingCart實例並注入依賴

ShoppingCart cart = new ShoppingCart(calcImpl); // 計算商品總價錢並輸出結果

Console.WriteLine("Total: {0:c}", cart.CalculateStockValue());

...

 

Ninject的使用的通常步驟就是這樣。該示例可正確輸出以下結果:

但看上去Ninject的使用好像使得編碼變得更加煩瑣,朋友們會問,直接使用下面的代碼不是更簡單嗎:

...

IValueCalculator calcImpl = new LinqValueCalculator();

ShoppingCart cart = new ShoppingCart(calcImpl);

Console.WriteLine("Total: {0:c}", cart.CalculateStockValue());

...

的確,對於單個簡單的DI,用Ninject確實顯得麻煩。但若是添加多個複雜點的依賴關係,使用Ninject則可大大提升編碼的工做效率。

13.5 Ninject如何提升編碼效率

當咱們請求Ninject建立某個類型的實例時,它會檢查這個類型和其它類型之間的耦合關係。若是存在依賴關係,那麼Ninject會根據依賴處理理它們,並建立全部所需類的實例。爲了解釋這句話和說明使用Ninject編碼的便捷,咱們再建立一個接口IDiscountHelper和一個實現該接口的類DefaultDiscountHelper,代碼以下:

 

//折扣計算接口public interface IDiscountHelper {

    decimal ApplyDiscount(decimal totalParam);

}

//默認折扣計算器public class DefaultDiscountHelper : IDiscountHelper {

    public decimal ApplyDiscount(decimal totalParam) {

        return (totalParam - (1m / 10m * totalParam));

    }

}

 

IDiscounHelper接口聲明瞭ApplyDiscount方法,DefaultDiscounterHelper實現了該接口,並定義了打9折的ApplyDiscount方法。而後咱們能夠把IDiscounHelper接口做爲依賴添加到LinqValueCalculator類中。代碼以下:

 

public class LinqValueCalculator : IValueCalculator {

    private IDiscountHelper discounter;

 

    public LinqValueCalculator(IDiscountHelper discountParam) {

        discounter = discountParam;

    }

 

    public decimal ValueProducts(params Product[] products) {

        return discounter.ApplyDiscount(products.Sum(p => p.Price));

    }

}

 

LinqValueCalculator類添加了一個用於接收IDiscountHelper接口的實現的構造函數,而後在ValueProducts方法中調用該接口的ApplyDiscount方法對計算出的商品總價錢進行打折處理,並返回折後總價。

到這,咱們先來畫個圖理一理ShoppingCart、LinqValueCalculator、IValueCalculator以及新添加的IDiscountHelper和DefaultDiscounterHelper之間的關係:

 

以此,咱們還能夠添加更多的接口和實現接口的類,接口和類愈來愈多時,它們的關係圖看上去會像一個依賴「鏈」,和生物學中的分子結構圖差很少。

按照前面說的使用Ninject的「二個步驟」,如今咱們在Main中的方法中編寫用於計算購物車中商品折後總價錢的代碼,以下所示:

 

 1 class Program { 2     static void Main(string[] args) { 3         IKernel ninjectKernel = new StandardKernel(); 4  5         ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); 6         ninjectKernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>(); 7  8         IValueCalculator calcImpl = ninjectKernel.Get<IValueCalculator>(); 9         ShoppingCart cart = new ShoppingCart(calcImpl);10         Console.WriteLine("Total: {0:c}", cart.CalculateStockValue());11         Console.ReadKey();12     }13 }

 

輸出結果:

代碼一目瞭然,雖然新添加了一個接口和一個類,但Main方法中只增長了第6行一句代碼,獲取實現IValueCalculator接口的對象實例的代碼不須要作任何改變。
定位到代碼的第8行,這一行代碼,Ninject爲咱們作的事是:
  當咱們須要使用IValueCalculator接口的實現時(經過Get方法),它便爲咱們建立LinqValueCalculator類的實例。而當建立LinqValueCalculator類的實例時,它檢查到這個類依賴IDiscountHelper接口。因而它又建立一個實現了該接口的DefaultDiscounterHelper類的實例,並經過構造函數把該實例注入到LinqValueCalculator類。而後返回LinqValueCalculator類的一個實例,並賦值給IValueCalculator接口的對象(第8行的calcImpl)。

總之,無論依賴「鏈」有多長有多複雜,Ninject都會按照上面這種方式檢查依賴「鏈」上的每一個接口和實現接口的類,並自動建立所須要的類的實例。在依賴「鏈」越長越複雜的時候,更能顯示使用Ninject編碼的高效率。

13.6 Ninject的綁定方式

我我的將Ninject的綁定方式分爲:通常綁定、指定值綁定、自我綁定、派生類綁定和條件綁定。這樣分類有點牽強,只是爲了本文的寫做須要和方便讀者閱讀而分,並非官方的分類。

一、通常綁定

在前文的示例中用Bind和To方法把一個接口綁定到實現該接口的類,這屬於通常的綁定。經過前文的示例相信你們已經掌握了,在這就再也不累述。

二、指定值綁定

咱們知道,經過Get方法,Ninject會自動幫咱們建立咱們所須要的類的實例。但有的類在建立實例時須要給它的屬性賦值,以下面咱們改造了一下的DefaultDiscountHelper類:

public class DefaultDiscountHelper : IDiscountHelper {

    public decimal DiscountSize { get; set; }

 

    public decimal ApplyDiscount(decimal totalParam) {

        return (totalParam - (DiscountSize / 10m * totalParam));

    }

}

DefaultDiscountHelper類添加了一個DiscountSize屬性,實例化時須要指定折扣值(DiscountSize屬性值),否則ApplyDiscount方法就沒意義。而實例化的動做是Ninject自動完成的,怎麼告訴Ninject在實例化類的時候給某屬性賦一個指定的值呢?這時就須要用到參數綁定,咱們在綁定的時候能夠經過給WithPropertyValue方法傳參的方式指定DiscountSize屬性的值,以下代碼所示:

 

public static void Main() {

    IKernel ninjectKernel = new StandardKernel();

 

    ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();

    ninjectKernel.Bind<IDiscountHelper>()

        .To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize", 5M);

 

    IValueCalculator calcImpl = ninjectKernel.Get<IValueCalculator>();

    ShoppingCart cart = new ShoppingCart(calcImpl);

    Console.WriteLine("Total: {0:c}", cart.CalculateStockValue());

    Console.ReadKey();

}

 

只是在Bind和To方法後添加了一個WithPropertyValue方法,其餘代碼都不用變,再一次見證了用Ninject編碼的高效。
WithPropertyValue方法接收了兩個參數,一個是屬性名(示例中的"DiscountSize"),一個是屬性值(示例中的5)。運行結果以下:

若是要給多個屬性賦值,則能夠在Bind和To方式後添加多個WithPropertyValue(<屬性名>,<屬性值>)方法。

咱們還能夠在類的實例化的時候爲類的構造函數傳遞參數。爲了演示,咱們再把DefaultDiscountHelper類改一下:

 

public class DefaultDiscountHelper : IDiscountHelper {

    private decimal discountRate;

 

    public DefaultDiscountHelper(decimal discountParam) {

        discountRate = discountParam;

    }

 

    public decimal ApplyDiscount(decimal totalParam) {

        return (totalParam - (discountRate/ 10m * totalParam));

    }

}

 

顯然,DefaultDiscountHelper類在實例化的時候必須給構造函數傳遞一個參數,否則程序會出錯。和給屬性賦值相似,只是用的方法是WithConstructorArgument(<參數名>,<參數值>),綁定方式以下代碼所示:

...

ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();

ninjectKernel.Bind<IDiscountHelper>()

    .To< DefaultDiscountHelper>().WithConstructorArgument("discountParam", 5M);

...

一樣,只須要更改一行代碼,其餘代碼原來怎麼寫仍是怎麼寫。若是構造函數有多個參數,則需在Bind和To方法後面加上多個WithConstructorArgument便可。

3.自我綁定

Niject的一個很是好用的特性就是自綁定。當經過Bind和To方法綁定好接口和類後,能夠直接經過ninjectKernel.Get<類名>()來得到一個類的實例。

在前面的幾個示例中,咱們都是像下面這樣來建立ShoppingCart類實例的:

...

IValueCalculator calcImpl = ninjectKernel.Get<IValueCalculator>();

ShoppingCart cart = new ShoppingCart(calcImpl);

...

其實有一種更簡單的定法,以下:

...

ShoppingCart cart = ninjectKernel.Get<ShoppingCart>();

...

這種寫法不須要關心ShoppingCart類依賴哪一個接口,也不須要手動去獲取該接口的實現(calcImpl)。當經過這句代碼請求一個ShoppingCart類的實例的時候,Ninject會自動判斷依賴關係,併爲咱們建立所需接口對應的實現。這種方式看起來有點怪,其實中規中矩的寫法是:

...

ninjectKernel.Bind<ShoppingCart>().ToSelf();

ShoppingCart cart = ninjectKernel.Get<ShoppingCart>();

...

這裏有自我綁定用的是ToSelf方法,在本示例中能夠省略該句。但用ToSelf方法自我綁定的好處是能夠在其後面用WithXXX方法指定構造函數參數、屬性等等的值。

4.派生類綁定

經過通常綁定,當請求一個接口的實現時,Ninject會幫咱們自動建立實現接口的類的實例。咱們說某某類實現某某接口,也能夠說某某類繼承某某接口。若是咱們把接口看成一個父類,是否是也能夠把父類綁定到一個繼承自該父類的子類呢?咱們來實驗一把。先改造一下ShoppingCart類,給它的CalculateStockValue方法改爲虛方法:

 

public class ShoppingCart {

    protected IValueCalculator calculator;

    protected Product[] products;

 

    //構造函數,參數爲實現了IEmailSender接口的類的實例

    public ShoppingCart(IValueCalculator calcParam) {

        calculator = calcParam;

        products = new[]{

            new Product {Name = "西瓜", Category = "水果", Price = 2.3M},

            new Product {Name = "蘋果", Category = "水果", Price = 4.9M},

            new Product {Name = "空心菜", Category = "蔬菜", Price = 2.2M},

            new Product {Name = "地瓜", Category = "蔬菜", Price = 1.9M}

        };

    }

 

    //計算購物車內商品總價錢

    public virtual decimal CalculateStockValue() {

        //計算商品總價錢

        decimal totalValue = calculator.ValueProducts(products);

        return totalValue;

    }

}

 

再添加一個ShoppingCart類的子類:

 

public class LimitShoppingCart : ShoppingCart {

    public LimitShoppingCart(IValueCalculator calcParam)

        : base(calcParam) {

    }

 

    public override decimal CalculateStockValue() {

        //過濾價格超過了上限的商品

        var filteredProducts = products.Where(e => e.Price < ItemLimit);

 

        return calculator.ValueProducts(filteredProducts.ToArray());

    }

 

    public decimal ItemLimit { get; set; }

}

 

而後把父類ShoppingCart綁定到子類LimitShoppingCart:

 

public static void Main() {

    IKernel ninjectKernel = new StandardKernel();

 

    ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();

    ninjectKernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>()

        .WithPropertyValue("DiscountSize", 5M);

    //派生類綁定

    ninjectKernel.Bind<ShoppingCart>().To<LimitShoppingCart>()

        .WithPropertyValue("ItemLimit", 3M);

 

    ShoppingCart cart = ninjectKernel.Get<ShoppingCart>();

    Console.WriteLine("Total: {0:c}", cart.CalculateStockValue());

    Console.ReadKey();

}

 

運行結果:

從運行結果能夠看出,cart對象調用的是子類的CalculateStockValue方法,證實了能夠把父類綁定到一個繼承自該父類的子類。經過派生類綁定,當咱們請求父類的時候,Ninject自動幫咱們建立一個對應的子類的實例,並將其返回。因爲抽象類不能被實例化,因此派生類綁定在使用抽象類的時候很是有用。

5.條件綁定

當一個接口有多個實現或一個類有多個子類的時候,咱們能夠經過條件綁定來指定使用哪個實現或子類。爲了演示,咱們給IValueCalculator接口再添加一個實現,以下:

 

public class IterativeValueCalculator : IValueCalculator {

 

    public decimal ValueProducts(params Product[] products) {

        decimal totalValue = 0;

        foreach (Product p in products) {

            totalValue += p.Price;

        }

        return totalValue;

    }

}

 

IValueCalculator接口如今有兩個實現:IterativeValueCalculator和LinqValueCalculator。咱們能夠指定,若是是把該接口的實現注入到LimitShoppingCart類,那麼就用IterativeValueCalculator,其餘狀況都用LinqValueCalculator。以下所示:

 

public static void Main() {

    IKernel ninjectKernel = new StandardKernel();

 

    ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();

    ninjectKernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>()

        .WithPropertyValue("DiscountSize", 5M);

    //派生類綁定

    ninjectKernel.Bind<ShoppingCart>().To<LimitShoppingCart>()

        .WithPropertyValue("ItemLimit", 3M);

    //條件綁定

    ninjectKernel.Bind<IValueCalculator>()

        .To<IterativeValueCalculator>().WhenInjectedInto<LimitShoppingCart>();

 

    ShoppingCart cart = ninjectKernel.Get<ShoppingCart>();

    Console.WriteLine("Total: {0:c}", cart.CalculateStockValue());

    Console.ReadKey();

}

 

運行結果:

運行結果是6.4,說明沒有打折,即調用的是計算方法是IterativeValueCalculator的ValueProducts方法。可見,Ninject會查找最匹配的綁定,若是沒有找到條件綁定,則使用默認綁定。在條件綁定中,除了WhenInjectedInto方法,還有When和WhenClassHas等方法,朋友們能夠在使用的時候再慢慢研究。

13.7 ASP.NET MVC中使用Ninject

本文用控制檯應用程序演示了Ninject的使用,但要把Ninject集成到ASP.NET MVC中仍是有點複雜的。首先要作的事就是建立一個繼承System.Web.Mvc.DefaultControllerFactory的類,MVC默認使用這個類來建立Controller類的實例(後續博文會專門講這個)。代碼以下:

 

 

using System;using Ninject;using System.Web.Mvc;using System.Web.Routing;

namespace MvcApplication1 {

    public class NinjectControllerFactory : DefaultControllerFactory {

        private IKernel ninjectKernel;

        public NinjectControllerFactory() {

            ninjectKernel = new StandardKernel();

            AddBindings();

        }

        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) {

            return controllerType == null ? null : (IController)ninjectKernel.Get(controllerType);

        }

        private void AddBindings() {

            // 在這添加綁定,

            // 如:ninjectKernel.Bind<IProductRepository>().To<FakeProductRepository>();        }

    }

}

 

NinjectControllerFactory

如今暫時不解釋這段代碼,你們都看懂就看,看不懂就過,只要知道在ASP.NET MVC中使用Ninject要作這麼一件事就行。

添加完這個類後,還要作一件事,就是在MVC框架中註冊這個類。通常咱們在Global.asax文件中的Application_Start方法中進行註冊,以下所示:

 

protected void Application_Start() {

    AreaRegistration.RegisterAllAreas();

 

    WebApiConfig.Register(GlobalConfiguration.Configuration);

    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

    RouteConfig.RegisterRoutes(RouteTable.Routes);

 

    ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());

}

 

註冊後,MVC框架就會用NinjectControllerFactory類去獲取Cotroller類的實例。在後續博文中會具體演示如何在ASP.NET MVC中使用Ninject,這裏就不具體演示了,你們知道須要作這麼兩件事就行。

雖然咱們前面花了很大功夫來學習Ninject就是爲了在MVC中使用這樣一個NinjectControllerFactory類,可是瞭解Ninject如何工做是很是有必要的。理解好了一種DI容器,可使得開發和測試更簡單、更高效。

 

14 LINQ的查詢的延遲執行

LINQ的查詢只在結果被使用,被枚舉的狀況才執行。參見下面的例子,體會。

 

public ViewResult FindProducts() {
Product[] products = {
new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
};
var foundProducts = products.OrderByDescending(e => e.Price)
.Take(3)
.Select(e => new {
e.Name,
e.Price
});
products[2] = new Product { Name = "Stadium", Price = 79600M };
StringBuilder result = new StringBuilder();
foreach (var p in foundProducts) {
result.AppendFormat("Price: {0} ", p.Price);
}
return View("Result", (object)result.ToString());
}
..

 

 

 

 

15 Razor

<tr>
<td>Stock Level</td>
<td>
@if (ViewBag.ProductCount == 0) {
@:Out of Stock
Figure 5-12. Using a switch statement in a Razor view
Chapter 5 ■ Working With razor
115
} else if (ViewBag.ProductCount == 1) {
<b>Low Stock (@ViewBag.ProductCount)</b>
} else {
@ViewBag.ProductCount
}
</td>
</tr>

 

C#語句中不須要加@,但在返回的html體裏須要加@

 

16 MVC路由規則

namespace UrlsAndRoutes {
public class RouteConfig {
public static void RegisterRoutes(RouteCollection routes) {
routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index",
id = "DefaultId" });
}
}
}

 

該路由有三個默認值,所以該規則匹配任何0到三個URL字段的url。匹配的原則是從左到右匹配。

URL

controller

action

id

http://loca:8081

Home

Index

DefaultId

http://loca:8081/a

a

Index

DefaultId

http://loca:8081/a/b

a

b

DefaultId

http://loca:8081/a/b/c

a

b

c

 

 

17 ActionLink

@Html.ActionLink("This is an outgoing URL",  //連接顯示文本
"Index", "Home", null,           //  /Action/Controller,其餘//=null
new {id = "myAnchorID",@class = "myCSSClass"} //屬性 id= 「」,class=」」

)

 

18 理解authentication

OnAuthenticationChallenge 在全部其餘filters前調用。

調用時機:1)在鑑權失敗(OnAuthentication設置鑑權失敗)時會調用,觸發用戶質詢,見3流程。成功不會調用,因此6後沒有調用。

          2)在Action執行完成後,Result返回前調用。此Action即被Authentication屬性修飾的action.見流程8

Pro Asp.net mvc 5一章的執行流程以下:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

19 Filters執行順序

The sequence is authentication filters, authorization filters,
action filters, and then result filters. The framework executes exception filters at any stage if there is an unhandled exception.

 

 

20 html編碼的問題

 

public static MvcHtmlString DisplayMessage(this HtmlHelper html, string msg) {

string result = String.Format("This is the message: <p>{0}</p>", msg);

return new MvcHtmlString(result);

}

result被MvcHtmlString處理後,直接被當成html標籤字符串。所以,靜razor渲染後,就變成了以下:

 

 

 

public static string DisplayMessage(this HtmlHelper html, string msg) {

return String.Format("This is the message: <p>{0}</p>", msg);

}

而此處,直接傳遞給razor是字符串,鑑於razor的智能化,它認爲是要渲染以下的字符串,因此,就本身encode處理後,變成純字符串展現:

 

 

分而治之,部分須要按字符串展現如:<input>,部分須要安裝html標籤語言展現如:<p><p>,所以分別處理,這樣p標籤被展現成段落,而input被展現成純字符串;

 

public static MvcHtmlString DisplayMessage(this HtmlHelper html,

string msg)

{
string encodedMessage = html.Encode(msg);
string result = String.Format("This is the message: <p>{0}</p>", encodedMessage);
return new MvcHtmlString(result);
}

 

 21 HtmlHelper

內聯自定義的helper能夠推斷參數的類型;而外部helper須要顯示cast

相關文章
相關標籤/搜索