Top Ten Traps in C# for C++ Programmers
http://www.ondotnet.com/pub/a/dotnet/2002/02/11/csharp_traps.html?page=1
1. Application Domain And Thread:
An application domain forms an isolation boundary for security, versioning, reliability, and unloading of managed code. Threads are the operating system construct used by the CLR to execute code. At run time, all managed code is loaded into an application domain and is run by a managed thread.
There is not a one-to-one correlation between application_domains and threads. Several threads can be executed in a single application domain at any given time, and a particular thread is not confined to a single application domain. That is, threads are free to cross application domain boundries. At any given time, every thread is executing in an application domain. Zero or one or more than one threads might be executing in any give application domain.
Application domains provide a more secure and versatile unit of processing that the common language runtime can use to provide isolation between applications. You can run several application domains in a single process with the same level of isolation that would exist in separate processes, but without incurring the additional overhead of making cross-process calls or switching between processes. The ability to run multiple applications within a single process dramatically increases server scalability.
因为我们既可以在一个线程中, 生成多个Application Domain. 也可以在一个Application Domain中启动多个线程, 因此线程和App Domain其实没有什么一对一的联系.
在一个进程中可以创建多个Application domain(甚至在一个线程就可以创建多个Application domain), 同时在一个Application Domain中也可以加载多个Application. 这两个特点都使得在一个Process中可以运行多个Application. 这里的Application是指外部的一个Assembly文件, 这个文件可以是.exe程序. 加载外部程序可以System.Reflection命名空间中的多个方法完成, 下面是常用的几个方法:
方法A: 先使用Assembly.LoadFrom(), 然后用CreateInstance() 方法创建对象, 最后在调用对象的方法
方法B: MyAppDomain.ExecuteAssembly(), 直接进入外部Assembly程序的入口.
方法C: NewAppDomain.CreateInstanceFrom(), 创建实例, 并执行你想执行的方法
引入App domain的目的是, 增强程序的健壮性, 比如, 一个Process有两个App domain, 即使一个App domain 发生错误, 也不会影响另一个domain, 因为它们之间是有boundary. Default domain在Process的运行期是不能Unload的,其他app domain是可以被unload的.
2. 关于访问修饰符
C#总共有5种访问修饰符.
Declared accessibility | Meaning | |
public | Access is not restricted. | |
protected internal | Access is limited to the current assembly or types derived from the containing class. | |
protected |
| |
internal |
| |
private |
|
对于类的成员可以使用所有的5种修饰符.
Namespaces没有访问的限制, 所以不能加任何的访问修饰符.
顶层类型(指哪些不是包含在其他类型的类型), 仅有2种可选的访问控制符, Internal 和 Public, 缺省的访问控制符为internal.
嵌套类型的访问控制符可以有更多的选择, 详见下表:
Members of | Default member accessibility | Allowed declared accessibility of the member | |
enum | public | None | |
class | private | public protected internal private protected internal | |
interface | public | None | |
struct | private |
|
3. 关于Lock
c#的lock, 其实是System.Threading.Monitor的一个简单包装, 就像Using()语句一样. 进入lock(someObject)时,就是Monitor.Enter(someObject); 退出Lock, 就是Monitor.Exit(someObject).
下面的这篇对Lock讲的非常透彻,
http://msdn2.microsoft.com/en-gb/magazine/cc188793.aspx
http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/dnaraskdr/html/askgui06032003.asp
http://visualstudiomagazine.com/columns/article.aspx?editorialsid=2173
http://www.cnblogs.com/city22/archive/2007/01/30/634948.html
看下面的代码:
lock(someObject)
{
DoSomething();
}
以前的误解是, 代码要锁住someObject. 但对于lock(tyteof(MyClass)), 就难以理解了. 其实这里的lock可以看作是名词"锁子", 而不是动词"锁住", 这样上面的代码可以理解为: 线程请求一个锁子, 锁子的名称为someObject, 锁子在一个时刻只能被一个线程所拥有, 而锁住的东西是后面的代码块, 这个代码段将会强行地以serialzed 方式执行.
我们常见到这样的代码, 在某个实例方法中, 是这样使用lock的:
public void SyncInBadWay()
{
lock(this)
{
// do something
}
}
在某个static方法中是这样使用lock的:
public static void StaticSyncInBadWay()
{
lock(typeof(MyClass))
{
// do something
}
}
首先讲, 上面两个写法都可以保证Thread safe. 但它们的执行效率是非常差的, 甚至会造成程序dead lock.
4. 关于Singleton模式的几个误区
网上有很多Singleton模式的样板, 多数是错误的, 下面的文档提出了很好的样板.
http://www.yoda.arachsys.com/csharp/singleton.html
http://codebetter.com/blogs/steve.hebert/archive/2005/12/16/135697.aspx
5. Instance constructor
An instance constructor is a class member that implements actions required to initialized an instance of a class. Declaring a constructor takes the following forms:
[attributes] [modifiers] class-name (formal-parameter-list) : base (argument-list) { ... }
[attributes] [modifiers] class-name (formal-parameter-list) : this (argument-list) { ... }
Instance constructors are not inherited. Hence, a class has no instance constructors other than those defined in the class. If a class contains no instance constructor declaration, a default constructor (one that takes no parameter) is provided automatically.
一般的实例方法(private型除外), 在基类中声明后, 在派生类中, 可以直接使用它们, 因为它们被派生类自动继承. 而基类的实例构造子是不能被派生类自动继承的. 如果派生类的构造子想使用基类的某个构造子, 必须在派生类构造子名称后使用base(args), 这种写法的意思是: 派生类的构造子首先调用基类的构造子, 在执行完基类构造子之后, 才去执行派生类构造子的其他代码.
没有参数的构造子叫作default constructor. 如果一个类没有声明构造子, CLR 会自动为它提供一个default construtor.
如果只有派生类定义了构造子的话, 在构造子中只需要初始化派生类中新增的成员, 对象的基类部分会使用基类的default construtor来初始化.
[attributes] [extern] class-name () { ... } // Note that access modifiers and parameters are not permitted
The body of a static constructor specified all the statements required to initialize the class. Note that static constructors are:
- Cannot inherited.
- Cannot be called directly.
- Are parameterless (hence cannot be overloaded.)
The exact timing of when a static constructor is called is implementation-dependent. However, it is subject to the following rules:
- The static constructor of a class executes before any instance of the class is created.
- The static constructor of a class executes before any of the static members of the class (if any) are referenced.
- The static constructor of a class executes after the static field initializers (if any) for the class.
- The static constructor of a class executes at most one time during the lifetime of a program.
7. destructor of Class
A destructor is class member that implements actions required to destroy an instance of a class.
~class-name () { ... } // Note that access modifiers and parameters are not permitted
Destructors are similar to static constructors in two areas:
- Destructors are not inherited.
- Cannot be called directly.
- Destructors are parameterless (hence cannot be overloaded.)
Destructors cannot be invoked directly. It is up to the garbage collector to decide when to invoke the destructor.
What happens if an exception is thrown and not caught in a destructor? Destructor execution is terminated and the destructor of the base class is called. If there is no base class, the exception is discarded.
在.net中, 其实构造子是被转化成Finalizer函数了. 也就是说, GC不是直接调用析构子的, 调用的是类的Finalizer.
~MyClass()
{
// do something
}
protected void override Finalize()
{
try
{
// do something
}
finally
{
base.Finalize();
}
}
7. unmanaged resource 的释放.
.net 中, 一般的对象都不需要我们关心对象的释放. 但对于unmanaged对象, 我们要负责及时释放, 这包括File, FileStream, XmlWriter, DbConnection, GDI+的Font等对象.
在.net中, 不要企图仅仅依靠使用析构子来释放类所占的关键资源, 这主要是因为析构子不能被显式调用, 它是由GC调用的, 而我们无法确定GC何时被激活. 析构子的资源释放动作仅仅可用作最后的补救.
最好是实现IDisposable, 通过Dispose()来释放资源, 因为Dispose()方法是可以被显式调用的. 如果是在Dispose()编写了释放了unmanaged resource的代码, 最好是通过GC.SuppressFinalize(this)来
通知GC不要再调用Finalizer了).
using System;
class Testing : IDisposable
{
bool is_disposed = false;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected virtual void Dispose(bool disposing)
{
if (!is_disposed) // only dispose once!
{
//to release unmanaged resource
ReleaseUnmanagedResource();
//to release managed resource
if (disposing)
{ReleaseManagedResource();
}
// perform cleanup for this object
Console.WriteLine("Disposing...");
}
this.is_disposed = true;
}
public void Dispose()
{
Dispose(true);
// tell the GC not to finalize
GC.SuppressFinalize(this);
}
~Testing()
{
//因为使用Testing类的程序员, 有可能忘记调用Dispose()方法来释放该类的所占有的资源, 这样可以在中调用Dispose()来释放资源, 来确保资源被释放.
Dispose(false);
Console.WriteLine("In destructor.");
}
}