C#入门06:大杂烩-MSDN上的C#编程指南

构造函数

静态构造函数用于初始化任何静态数据,或用于执行仅需执行一次的特定操做。在建立第一个实例或引用任何静态成员以前,将自动调用静态构造函数。程序员

class SimpleClass
{
    // Static constructor
    static SimpleClass()
    {
        //...
    }
}

静态构造函数具备如下特色:
静态构造函数既没有访问修饰符,也没有参数。
在建立第一个实例或引用任何静态成员以前,将自动调用静态构造函数来初始化类。
没法直接调用静态构造函数。
在程序中,用户没法控制什么时候执行静态构造函数。
静态构造函数的典型用途是:当类使用日志文件时,将使用这种构造函数向日志文件中写入项。
静态构造函数在为非托管代码建立包装类时也颇有用,此时该构造函数能够调用 LoadLibrary 方法。编程

与有些语言不一样,C# 不提供复制构造函数。若是您建立了新的对象并但愿从现有对象复制值,您必须自行编写适当的方法。设计模式

class Person
    {
        private string name;
        private int age;

        // Copy constructor.
        public Person(Person previousPerson)
        {
            name = previousPerson.name;
            age = previousPerson.age;
        }
    }

析构函数

析构函数用于析构类的实例。
不能在结构中定义析构函数。只能对类使用析构函数。
一个类只能有一个析构函数。
没法继承或重载析构函数。
没法调用析构函数。它们是被自动调用的。
析构函数既没有修饰符,也没有参数。
例如,下面是类 Car 的析构函数的声明:数组

class Car
{
    ~ Car()  // destructor
    {
        // cleanup statements...
    }
}

该析构函数隐式地对对象的基类调用 Finalize。这样,前面的析构函数代码被隐式地转换为:安全

protected override void Finalize()
{
    try
    {
        // cleanup statements...
    }
    finally
    {
        base.Finalize();
    }
}

这意味着对继承链中的全部实例递归地调用 Finalize 方法(从派生程度最大的到派生程度最小的)。
不该使用空析构函数。若是类包含析构函数,Finalize 队列中则会建立一个项。调用析构函数时,将调用垃圾回收器来处理该队列。若是析构函数为空,则只会致使没必要要的性能丢失。
程序员没法控制什么时候调用析构函数,由于这是由垃圾回收器决定的。垃圾回收器检查是否存在应用程序再也不使用的对象。若是垃圾回收器认为某个对象符合析构,则调用析构函数(若是有)并回收用来存储此对象的内存。程序退出时也会调用析构函数。
资源的显示释放
若是您的应用程序在使用昂贵的外部资源,则还建议您提供一种在垃圾回收器释放对象前显式地释放资源的方式。可经过实现来自 IDisposable 接口的 Dispose 方法来完成这一点,该方法为对象执行必要的清理。这样可大大提升应用程序的性能。即便有这种对资源的显式控制,析构函数也是一种保护措施,可用来在对 Dispose 方法的调用失败时清理资源。
1)实现 Dispose 方法;
2)使用using 语句。app

静态类和成员

静态类和类成员用于建立无需建立类的实例就可以访问的数据和函数。静态类成员可用于分离独立于任何对象标识的数据和行为:不管对象发生什么更改,这些数据和函数都不会随之变化。当类中没有依赖对象标识的数据或行为时,就可使用静态类。
类能够声明为 static 的,以指示它仅包含静态成员。不能使用 new 关键字建立静态类的实例。静态类在加载包含该类的程序或命名空间时由 .NET Framework 公共语言运行库 (CLR) 自动加载。
使用静态类来包含不与特定对象关联的方法。例如,建立一组不操做实例数据而且不与代码中的特定对象关联的方法是很常见的要求。您应该使用静态类来包含那些方法。
静态类的主要功能以下:
它们仅包含静态成员。
它们不能被实例化。
它们是密封的。
它们不能包含实例构造函数(C# 编程指南)。
所以建立静态类与建立仅包含静态成员和私有构造函数的类大体同样。私有构造函数阻止类被实例化。
使用静态类的优势在于,编译器可以执行检查以确保不致偶然地添加实例成员。编译器将保证不会建立此类的实利。
静态类是密封的,所以不可被继承。静态类不能包含构造函数,但仍可声明静态构造函数以分配初始值或设置某个静态状态。有关更多信息,请参见静态构造函数(C# 编程指南)。异步

静态成员
即便没有建立类的实例,也能够调用该类中的静态方法、字段、属性或事件。若是建立了该类的任何实例,不能使用实例来访问静态成员。只存在静态字段和事件的一个副本,静态方法和属性只能访问静态字段和静态事件。静态成员一般用于表示不会随对象状态而变化的数据或计算;例如,数学库可能包含用于计算正弦和余弦的静态方法。ide

//静态类中必须所有是静态成员函数
    static class CompanyInfo
    {
        public static readonly string name;
        public static readonly string addr;

        //使用静态构造函数初始化静态类成员
        static CompanyInfo()
        {
            name = "BGI";
            addr = "Shenzhen";
            Console.WriteLine("InitCompanyInfo");
        }
    }

索引器

索引器容许类或结构的实例按照与数组相同的方式进行索引。索引器相似于属性,不一样之处在于它们的访问器采用参数。
在下面的示例中,定义了一个泛型类,并为其提供了简单的 get 和 set 访问器方法(做为分配和检索值的方法)。Program 类为存储字符串建立了此类的一个实例。函数

class SampleCollection<T>
{
    private T[] arr = new T[100];
    public T this[int i]
    {
        get
        {
            return arr[i];
        }
        set
        {
            arr[i] = value;
        }
    }
}

// This class shows how client code uses the indexer
class Program
{
    static void Main(string[] args)
    {
        SampleCollection<string> stringCollection = new SampleCollection<string>();
        stringCollection[0] = "Hello, World";
        System.Console.WriteLine(stringCollection[0]);
    }
}

索引器使得对象可按照与数组类似的方法进行索引。性能

  • get 访问器返回值。set 访问器分配值。
  • this 关键字用于定义索引器。
  • value 关键字用于定义由 set 索引器分配的值。
  • 索引器没必要根据整数值进行索引,由您决定如何定义特定的查找机制。
  • 索引器可被重载。
  • 索引器能够有多个形参,例如当访问二维数组时。

提升索引器的安全性和可靠性有两种主要的方法:
1)当设置并检索来自索引器访问的任何缓冲区或数组的值时,请始终确保您的代码执行范围和类型检查。
2)应当为 get 和 set 访问器的可访问性设置尽量多的限制。这一点对 set 访问器尤其重要。
属性和索引器的区别: 输入图片说明

委托

委托相似于 C++ 函数指针,但它是类型安全的。
委托容许将方法做为参数进行传递。
委托可用于定义回调方法。
委托能够连接在一块儿;例如,能够对一个事件调用多个方法。
方法不须要与委托签名精确匹配。有关更多信息,请参见协变和逆变。
C# 2.0 版引入了匿名方法的概念,此类方法容许将代码块做为参数传递,以代替单独定义的方法。
与 C 中的函数指针不一样,委托是面向对象的、类型安全的和保险的。 常见的委托声明和调用:

// Create a method for a delegate.
public static void DelegateMethod(string message)
{
    System.Console.WriteLine(message);
}

// Instantiate the delegate.
Del handler = DelegateMethod;

// Call the delegate.
handler("Hello World");

委托调用方法:

1)命名方法

public delegate void TestDelegate();

    class Program
    {
        //打印当前日期和时间
        static void PrintPoint()
        {
            for (int i = 0; i < 5; i++)
            {
                DateTime dtCurr = DateTime.Now;
                Console.WriteLine("{0:yyyy-MM-dd HH:mm:ss.fff}", dtCurr);
                Thread.Sleep(1000);
            }
        }
    }

    //使用类Program的静态函数PrintPoint()实例化委托
    TestDelegate method = Program.PrintPoint;
    method();

2)匿名委托
在 2.0 以前的 C# 版本中,声明委托的惟一方法是使用命名方法。C# 2.0 引入了匿名方法。要将代码块传递为委托参数,建立匿名方法则是惟一的方法。例如:

// Create a handler for a click event
button1.Click += delegate(System.Object o, System.EventArgs e)
                   { System.Windows.Forms.MessageBox.Show("Click!"); };

或:

// Create a delegate instance
delegate void Del(int x);

// Instantiate the delegate using an anonymous method
Del d = delegate(int k) { /* ... */ };

若是使用匿名方法,则没必要建立单独的方法,所以减小了实例化委托所需的编码系统开销。例如,若是建立方法所需的系统开销是没必要要的,在委托的位置指定代码块就很是有用。启动新线程便是一个很好的示例。无需为委托建立更多方法,线程类便可建立一个线程而且包含该线程执行的代码。

void StartThread()
{
    System.Threading.Thread t1 = new System.Threading.Thread
      (delegate()
            {
                System.Console.Write("Hello, ");
                System.Console.WriteLine("World!");
            });
    t1.Start();
}

什么时候使用委托而不使用接口:
在如下状况中使用委托:
1)当使用事件设计模式时。
2)当封装静态方法可取时。
3)当调用方不须要访问实现该方法的对象中的其余属性、方法或接口时。
4)须要方便的组合。
5)当类可能须要该方法的多个实现时。

在如下状况中使用接口:
1)当存在一组可能被调用的相关方法时。
2)当类只须要方法的单个实现时。
3)当使用接口的类想要将该接口强制转换为其余接口或类类型时。
4)当正在实现的方法连接到类的类型或标识时:例如比较方法。

委托的协变:
委托协变指的是返回类型是一个基类类型,由调用者实现的函数返回类型为委托返回类型的派生类的一种委托调用模式:

class Mammals
{
}

class Dogs : Mammals
{
}

class Program
{
    // Define the delegate.
    public delegate Mammals HandlerMethod();

    public static Mammals FirstHandler()
    {
        return null;
    }

    public static Dogs SecondHandler()
    {
        return null;
    }

    static void Main()
    {
        HandlerMethod handler1 = FirstHandler;

        // Covariance allows this delegate.
        HandlerMethod handler2 = SecondHandler;
    }
}

委托逆变:
委托逆变和协变相反,是委托的参数是一个基类类型,而调用者传递的参数类型是委托参数类型的派生类:

System.DateTime lastActivity;
public Form1()
{
    InitializeComponent();

    lastActivity = new System.DateTime();
    this.textBox1.KeyDown += this.MultiHandler; //works with KeyEventArgs
    this.button1.MouseClick += this.MultiHandler; //works with MouseEventArgs

}

// Event hander for any event with an EventArgs or
// derived class in the second parameter
private void MultiHandler(object sender, System.EventArgs e)
{
    lastActivity = System.DateTime.Now;
}

事件

事件具备如下特色:
发行者肯定什么时候引起事件,订户肯定执行何种操做来响应该事件。
一个事件能够有多个订户。一个订户可处理来自多个发行者的多个事件。
没有订户的事件永远不会被调用。
事件一般用于通知用户操做(如:图形用户界面中的按钮单击或菜单选择操做)。
若是一个事件有多个订户,当引起该事件时,会同步调用多个事件处理程序。要异步调用事件,请参见使用异步方式调用同步方法。
能够利用事件同步线程。
在 .NET Framework 类库中,事件是基于 EventHandler 委托和 EventArgs 基类的。

发布符合 .NET Framework 准则的事件:
.NET Framework 类库中的全部事件均基于 EventHandler 委托,定义以下:

public delegate void EventHandler(object sender, EventArgs e);

事件只能在声明事件的类中使用,及时是派生类也不能使用,派生类只能订阅事件。若是须要在派生类中直接触发事件,就须要在基类中将事件的触发封装起来,变成一个protected方法,而后派生类就能够出发事件了:

//声明委托
    public delegate void PrintHandle(string msg);

    class Animal
    {
        //声明事件
        public event PrintHandle PrintEvent;

        //基类方法
        public virtual void Eat()
        {
            PrintHandle p = PrintEvent;
            if (p != null)
            {
                p(Name);
            }
        }

        //基类属性
        public virtual string Name { get; set; }
    }

    class Dog : Animal
    {
        public Dog()
        {
            Name = "Dog";
        }
    }

    class Program
    {
        static void Main()
        {
            Dog d = new Dog();
            d.PrintEvent += delegate (string msg)
            {
                Console.WriteLine("Your name is {0}", msg);
            };
            d.Eat();

            Console.Read();
        }
    }

迭代器

建立迭代器最经常使用的方法是对 IEnumerable 接口实现 GetEnumerator 方法,例如:

public System.Collections.IEnumerator GetEnumerator()
{
    for (int i = 0; i < max; i++)
    {
        yield return i;
    }
}

static void Main()
{
    ListClass listClass1 = new ListClass();

    foreach (int i in listClass1.GetEnumerator())
    {
        System.Console.WriteLine(i);
    }
}

带有参数的迭代器:

// Implementing the enumerable pattern
public System.Collections.IEnumerable SampleIterator(int start, int end)
{
    for (int i = start; i <= end; i++)
    {
        yield return i;
    }
}

ListClass test = new ListClass();
foreach (int n in test.SampleIterator(1, 10))
{
    System.Console.WriteLine(n);
}

使用不安全代码

下面的示例经过读取并显示一个文本文件来演示 Windows ReadFile 函数。ReadFile 函数须要使用 unsafe 代码,由于它须要一个做为参数的指针。
传递到 Read 函数的字节数组是托管类型。这意味着公共语言运行库 (CLR) 垃圾回收器可能会随意地对数组使用的内存进行从新定位。为了防止出现这种状况,使用 fixed 来获取指向内存的指针并对它进行标记,以便垃圾回收器不会移动它。在 fixed 块的末尾,内存将自动返回,以便可以经过垃圾回收移动。
此功能称为“声明式锁定”。锁定的好处是系统开销很是小,除非在 fixed 块中发生垃圾回收(但此状况不太可能发生)。

class FileReader
{
    const uint GENERIC_READ = 0x80000000;
    const uint OPEN_EXISTING = 3;
    System.IntPtr handle;

    [System.Runtime.InteropServices.DllImport("kernel32", SetLastError = true)]
    static extern unsafe System.IntPtr CreateFile
    (
        string FileName,          // file name
        uint DesiredAccess,       // access mode
        uint ShareMode,           // share mode
        uint SecurityAttributes,  // Security Attributes
        uint CreationDisposition, // how to create
        uint FlagsAndAttributes,  // file attributes
        int hTemplateFile         // handle to template file
    );

    [System.Runtime.InteropServices.DllImport("kernel32", SetLastError = true)]
    static extern unsafe bool ReadFile
    (
        System.IntPtr hFile,      // handle to file
        void* pBuffer,            // data buffer
        int NumberOfBytesToRead,  // number of bytes to read
        int* pNumberOfBytesRead,  // number of bytes read
        int Overlapped            // overlapped buffer
    );

    [System.Runtime.InteropServices.DllImport("kernel32", SetLastError = true)]
    static extern unsafe bool CloseHandle
    (
        System.IntPtr hObject // handle to object
    );

    public bool Open(string FileName)
    {
        // open the existing file for reading       
        handle = CreateFile
        (
            FileName,
            GENERIC_READ,
            0,
            0,
            OPEN_EXISTING,
            0,
            0
        );

        if (handle != System.IntPtr.Zero)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public unsafe int Read(byte[] buffer, int index, int count)
    {
        int n = 0;
        fixed (byte* p = buffer)
        {
            if (!ReadFile(handle, p + index, count, &n, 0))
            {
                return 0;
            }
        }
        return n;
    }

    public bool Close()
    {
        return CloseHandle(handle);
    }
}

class Test
{
    static int Main(string[] args)
    {
        if (args.Length != 1)
        {
            System.Console.WriteLine("Usage : ReadFile <FileName>");
            return 1;
        }

        if (!System.IO.File.Exists(args[0]))
        {
            System.Console.WriteLine("File " + args[0] + " not found.");
            return 1;
        }

        byte[] buffer = new byte[128];
        FileReader fr = new FileReader();

        if (fr.Open(args[0]))
        {
            // Assume that an ASCII file is being read.
            System.Text.ASCIIEncoding Encoding = new System.Text.ASCIIEncoding();

            int bytesRead;
            do
            {
                bytesRead = fr.Read(buffer, 0, buffer.Length);
                string content = Encoding.GetString(buffer, 0, bytesRead);
                System.Console.Write("{0}", content);
            }
            while (bytesRead > 0);

            fr.Close();
            return 0;
        }
        else
        {
            System.Console.WriteLine("Failed to open requested file");
            return 1;
        }
    }
}

性能影响

这里介绍两个会对性能有较大影响的操做: 1)装箱和拆箱 装箱和取消装箱都是须要大量运算的过程。对值类型进行装箱时,必须建立一个全新的对象。此操做所需时间可比赋值操做长 20 倍。取消装箱时,强制转换过程所需时间可达赋值操做的四倍。 2)不要建立空的析构函数 不该使用空析构函数。若是类包含析构函数,Finalize 队列中则会建立一个项。调用析构函数时,将调用垃圾回收器来处理该队列。若是析构函数为空,只会致使性能下降。

相关文章
相关标签/搜索