2019独角兽企业重金招聘Python工程师标准>>>
构造函数
静态构造函数用于初始化任何静态数据,或用于执行仅需执行一次的特定操作。在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数。
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 语句。
静态类和成员
静态类和类成员用于创建无需创建类的实例就能够访问的数据和函数。静态类成员可用于分离独立于任何对象标识的数据和行为:无论对象发生什么更改,这些数据和函数都不会随之变化。当类中没有依赖对象标识的数据或行为时,就可以使用静态类。
类可以声明为 static 的,以指示它仅包含静态成员。不能使用 new 关键字创建静态类的实例。静态类在加载包含该类的程序或命名空间时由 .NET Framework 公共语言运行库 (CLR) 自动加载。
使用静态类来包含不与特定对象关联的方法。例如,创建一组不操作实例数据并且不与代码中的特定对象关联的方法是很常见的要求。您应该使用静态类来包含那些方法。
静态类的主要功能如下:
它们仅包含静态成员。
它们不能被实例化。
它们是密封的。
它们不能包含实例构造函数(C# 编程指南)。
因此创建静态类与创建仅包含静态成员和私有构造函数的类大致一样。私有构造函数阻止类被实例化。
使用静态类的优点在于,编译器能够执行检查以确保不致偶然地添加实例成员。编译器将保证不会创建此类的实利。
静态类是密封的,因此不可被继承。静态类不能包含构造函数,但仍可声明静态构造函数以分配初始值或设置某个静态状态。有关更多信息,请参见静态构造函数(C# 编程指南)。
静态成员
即使没有创建类的实例,也可以调用该类中的静态方法、字段、属性或事件。如果创建了该类的任何实例,不能使用实例来访问静态成员。只存在静态字段和事件的一个副本,静态方法和属性只能访问静态字段和静态事件。静态成员通常用于表示不会随对象状态而变化的数据或计算;例如,数学库可能包含用于计算正弦和余弦的静态方法。
//静态类中必须全部是静态成员函数
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
{
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 stringCollection = new SampleCollection();
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 ");
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 队列中则会创建一个项。调用析构函数时,将调用垃圾回收器来处理该队列。如果析构函数为空,只会导致性能降低。