• C#学习笔记


    命名规则

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

    1.1 Pascal规则来命名属性、方法、事件和类名

    public class HelloWorld

     {

         public void SayHello(string name)

         {

         }

     }

      Pascal规则是指名称中单词的首字母大写 ,如EmployeeSalary、 ConfimationDialog、PlainTextEncoding。

    1.2 Camel规则来命名成员变量、局部变量和方法的参数

    public class Product

     {

         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的绑定方式分为:一般绑定、指定值绑定、自我绑定、派生类绑定和条件绑定。这样分类有点牵强,只是为了本文的写作需要和方便读者阅读而分,并不是官方的分类。

    1、一般绑定

    在前文的示例中用Bind和To方法把一个接口绑定到实现该接口的类,这属于一般的绑定。通过前文的示例相信大家已经掌握了,在这就不再累述。

    2、指定值绑定

    我们知道,通过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

  • 相关阅读:
    微软一站式技术框架(AllInOne Code Framework(AIO)) 登陆 MSDN中国首页
    The stepbystep guide of making a C# (or VB.NET) Windows 7 Trigger Start Service
    AllInOne Code Framework: 微软一站式开发技术框架 示例命名规则和目录结构说明
    Windows 7 新特性 Shell Library 编程接口介绍
    一步一步教你如何编写VC#,VB.NET或VC++代码玩转Windows Shell Known Folders
    微软一站式开发技术框架解决方案 年内展望
    AllInOne Code Framework: 微软一站式开发技术框架解决方案 2009730 新增sample code简介
    AllInOne Code Framework: 微软一站式开发技术框架 2009917 新增代码示例简介
    AllInOne Code Framework: 微软一站式开发技术框架解决方案 2009826 新增sample code简介
    微软 AllInOne Code Framework (AIO) 客户端应用程序 期待您的见解
  • 原文地址:https://www.cnblogs.com/dusf/p/10005905.html
Copyright © 2020-2023  润新知