看完了《A Developer’s Guide to the Microsoft® .NET Service Bus》,最后的Guidance总结得非常好,摘下来以备后用。
One of the hardest questions to answer when moving towards the .NET Service Bus is: which binding should I use? This section provides some general guidance to help answer that important question.
Microsoft recommends you use the NetTcpRelayBinding by default unless you specifically need a communication feature provided by one of the other bindings. This was the binding that I used in the HelloServiceBus example towards the beginning of the paper. The NetTcpRelayBinding provides the most compact wire format and the most efficient communication path through the relay service infrastructure. It will therefore yield the highest throughput of all the WCF relay bindings and it will impose the lowest amount of processing overhead on the listening service.
If your scenario requires transferring large messages in a peer-to-peer fashion, you should prefer the Hybird/Direct connectivity modes when using the NetTcpRelayBinding.
If you’re working in locked-down network environments, the NetTcpRelayBinding may not work given the outbound TCP ports it requires by listeners. In situations like this, you’ll want to turn to the NetOnewayRelayBinding or the NetEventRelayBinding. These bindings provide the most aggressive NAT/firewall traversal options because they provide an optional HTTP connectivity mode that enables listeners to receive messages over HTTP ports (80/443) that are usually unblocked. The main scenario for choosing the NetEventRelayBinding is when you need event distribution for multicast scenarios.
When your requirements call for interoperability with non-Microsoft clients, the NetTcpRelayBinding, NetOnewayRelayBinding, and NetEventRelayBinding are not going to work. This is when you should turn to the HTTP relay bindings. The HTTP relay bindings provide the highest-degree of interoperability with the rest of the world because they expose endpoints based on open standards including HTTP, SOAP 1.1, and SOAP 1.2. When you use the HTTP relay bindings, any compatible Web services client will be able to communicate with your .NET Service Bus endpoints. The primary downside to using the HTTP relay bindings is they come with the greatest amount of processing overhead on the listening service.
Choosing between the various HTTP relay bindings boils down to what style of service you’re trying to build (REST vs. SOAP) and what additional Web services protocols you want to support (WS-*). If your requirements call for REST, you want to use the WebHttpRelayBinding. If your requirements call for SOAP, you have several choices. You’ll want to use the BasicHttpRelayBinding when you’re building basic profile compliant SOAP services, and the WSHttpRelayBinding/WS2007HttpRelayBinding when you’re building more advanced SOAP-based services that leverage the layered WS-* protocols.
In the area of security, Microsoft encourages you to take full advantage of message-based security techniques by signing and encrypting your messages that will pass through the service relay (this is the “hybrid” mode I referred to in the Access Control section). The relay security model composes nicely with WS-Security thereby making it possible to ensure privacy and tamper protection. The service relay does not read or touch any of the information within the body of your messages – it only looks at the URI and, in some cases, the WS-Addressing headers in order to determine how to relay the message.
If you need to support asynchronous or disconnect scenarios where senders and receivers aren’t always running at the same time, you should turn towards .NET Service Bus queues. This also makes it possible to handle “elastic” throughput requirements when senders and receivers may not be able to process messages at the same rate. And when you need to support a publish/subscribe architecture, you should turn towards .NET Service Bus routers (and you may want to compose them with queues for durability). These two messaging primitives open up a gamut of new possibilities on the .NET Service Bus.
posted @
2009-06-21 14:17 董超 阅读(5) |
评论 (0) |
编辑
1) 新建一个Web Service工程
VS2008菜单:New/Web Site/ASP.NET Web Service
建好之后,在Solution Explorer的App_Code文件夹里有Service.cs这个文件,里面默认会有一个实现好的HelloWorld方法。
无需添加任何代码,直接调试或运行,会打开一个网页。在该网页地址的最后加上?wsdl,得到一个wsdl文档,将该文档保存下来,作为将来用wsdl.exe生成客户端代理源代码的输入文件。
2) 上面我们已经建好了服务端,并导出了wsdl文件,下面的任务是做一个客户端。VS自带的工具wsdl.exe可以生成客户端代理的源文件。它是个命令行工具,具体命令是:
> wsdl /namespace: 生成的源文件中客户端代理类的命名空间 WSDL文件路径
可以指定生成的源文件名和路径,在默认情况下,生成的源文件名是Service.cs。文件的路径也会在执行完命令后给出。
值得注意的是,使用wsdl.exe的时候必须要能访问互联网,因为生成源文件必须要参考很多命名空间。
3) 下面做一个简单的Console客户端,并调用Web Service的HelloWorld方法。
可以将客户端代理类文件加入项目,也可以先把它编译成dll再在项目中添加对它的引用。
添加引用System.Web.Services,并在Program.cs文件中引入刚才用wsdl.exe生成的客户端代理类的命名空间。然后在Main函数里添加下面代码:
Service server = new Service();
Console.WriteLine(server.HelloWorld());
此时只要WebService开启着,就能正确返回结果。
posted @
2009-04-16 10:27 董超 阅读(52) |
评论 (0) |
编辑
|
Rule
|
Description
|
Automatic Correction
|
|
Reference to a static variable should be synchronized using a thread lock.
|
Static members are shared across object instances and threads and should be protected using some form of data synchronization (lock, Monitor.Enter/Monitor.Exit, Synchronized methods, Interlocked methods, synchronization attributes, etc.). This rule detects unsafe operations on shared data and provides synchronization.
|
Yes
|
|
The update of a variable should be synchronized using Threading.Interlocked operations.
|
Unprotected integer and long references should be synchronized using the Interlocked methods such as Interlocked.Increment(), Interlocked.Decrement() and Interlocked.Exchange().
|
Yes
|
|
Redundant Locking exists.
|
Interlocked operations are already effectively synchronized. There is no need to synchronize a variable operation that is protected with the Interlocked class, with and additional lock statement.
|
Yes
|
|
Optimize the update synchronization of a variable by using Threading.Interlocked operations instead of a lock.
|
The Interlocked methods for locking single operations on integers and longs is more efficient than a lock statement.
|
Yes
|
|
A logical block of unsynchronized static variable references has been detected and should be synchronized using a lock.
|
More than just a single unprotected reference exists and requires synchronizing the chain of logical operations.
|
Yes
|
|
Releasing a lock in any other location than a 'finally' section of try/finally block may create threading issues.
|
Using a try/finally block ensures that locks managed using Monitor.Enter and Monitor.Exit are performed once and only once in all situations.
|
Yes
|
|
The released lock was not the last lock acquired. Locks acquired last should be released first.
|
Locks using Monitor.Enter() and Monitor.Exit() should always be released in the reverse order they were acquired. Incorrect order of acquiring and releasing locks is one of the major causes of deadlock scenarios in a multi-threaded application.
|
No
|
|
A lock on the same object has already been acquired.
|
There is typically no good reason to perform multiple locks on one object within the same thread using. If multiple locks are acquired within the same thread using Monitor.Enter(), then the same number of releases must be issued with Monitor.Exit(). This type of situation is indicative of deadlock scenarios.
|
No
|
|
A lock has been acquired but has not been released.
|
Acquiring a lock Monitor.Enter() and not releasing it with Monitor.Exit() is a series bug that creates a deadlock situation. Address this issue by safely releasing the lock when complete by using a Monitor.Exit with a try/finally block.
|
No
|
|
A lock has been released in multiple places.
|
If locks are being released using Monitor.Exit() in multiple locations it is a sign that the Monitor class may not be being used safely. If a lock is called for release after it has already been released a SynchronizedLockException will be thrown.
|
No
|
|
Avoid calling static methods that call static methods on the same class.
|
Performance and scalability issues can result when a static method in class calls another static method in the same class.
|
No
|
|
Avoid providing static methods that alter static state.
|
In general, it is a weak design practice to be updating static data frequently within a threaded application because of the need for synchronization. Altering static state from a static method increases this likelihood.
|
No
|
|
Use thread pooling classes to optimize multi-threaded applications.
|
Avoid using System.Threading.Thread class to create threads unless you have special needs such as managing thread priority. Instead usage of system thread pool (ThreadPool.QueueUserWorkItem) can improve your performance and scalability.
|
No
|
|
Do not terminate other threads using Thread.Abort.
|
Thread.Abort raises a ThreadAbortException. When called within a thread the action is very predictable, but when called on a thread externally, the effect may interrupt the processing anywhere - such as within a finally block. Use Thread.Join to wait until desired thread finishes.
|
No
|
|
Lock Must not be Obtained on a Value Type.
|
Montor.Enter() and Exit() calls must lock on an object of a reference type. They should never lock on an object of a value type such as object of type "int". Unlike the lock statement, the compiler will allow you to acquire a lock on an object of a value type.
|
No
|
|
Excessive locking - lock contained inside a loop.
|
Excessive locking or thread synchronization can cause significant scalability and performance issues in some applications and should be minimized. Locks inside a loop indicate the potential for excessive locking.
|
No
|
|
Large number of statements protected by a lock.
|
Excessive locking or thread synchronization can cause significant scalability and performance issues in some applications and should be minimized. The more statements a lock contains, the more likely it is to impact scalability.
|
No
|
|
Excessive nesting of locks.
|
Excessive locking or thread synchronization can cause significant scalability and performance issues in some applications and should be minimized. Nested locks mean multiple locks are occurring and are also the root of most deadlocking scenarios.
|
No
|
|
Excessive portion of class is synchronized.
|
Excessive locking or thread synchronization can cause significant scalability and performance issues in some applications and should be minimized. If a large portion of a class is synchronized it indicates that most references to the class will require synchronization and potentially impact scalability.
|
No
|
|
Excessive synchronized calls into same class.
|
Excessive locking or thread synchronization can cause significant scalability and performance issues in some applications and should be minimized. Method calls into the same class that synchronization occurs can be an indication of excessive synchronization.
|
No
|
posted @
2009-03-30 23:51 董超 阅读(17) |
评论 (0) |
编辑
因为C语言所有复杂的指针声明,都是由各种声明嵌套构成的。如何解读复杂指针声明呢?右左法则是一个既著名又常用的方法。不过,右左法则其实并不是C标准里面的内容,它是从C标准的声明规定中归纳出来的方法。C标准的声明规则,是用来解决如何创建声明的,而右左法则是用来解决如何辩识一个声明的,两者可以说是相反的。右左法则的英文原文是这样说的:
The right-left rule: Start reading the declaration from the innermost parentheses, go right, and then go left. When you
encounter parentheses, the direction should be reversed. Once everything in the parentheses has been
parsed, jump out of it. Continue till the whole declaration has been parsed.
这段英文的翻译如下:
右左法则:首先从最里面的圆括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。
笔者要对这个法则进行一个小小的修正,应该是从未定义的标识符开始阅读,而不是从括号读起,之所以是未定义的标识符,是因为一个声明里面可能有多个标识符,但未定义的标识符只会有一个。
现在通过一些例子来讨论右左法则的应用,先从最简单的开始,逐步加深:
int (*func)(int *p);
首先找到那个未定义的标识符,就是func,它的外面有一对圆括号,而且左边是一个*号,这说明func是一个指针,然后跳出这个圆括号,先看右边,也是一个圆括号,这说明(*func)是一个函数,而func是一个指向这类函数的指针,就是一个函数指针,这类函数具有int*类型的形参,返回值类型是 int。
int (*func)(int *p, int (*f)(int*));
func被一对括号包含,且左边有一个*号,说明func是一个指针,跳出括号,右边也有个括号,那么func是一个指向函数的指针,这类函数具有int *和int (*)(int*)这样的形参,返回值为int类型。再来看一看func的形参int (*f)(int*),类似前面的解释,f也是一个函数指针,指向的函数具有int*类型的形参,返回值为int。
int (*func[5])(int *p);
func右边是一个[]运算符,说明func是一个具有5个元素的数组,func的左边有一个*,说明func的元素是指针,要注意这里的*不是修饰 func的,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合,因此*修饰的是func[5]。跳出这个括号,看右边,也是一对圆括号,说明func数组的元素是函数类型的指针,它所指向的函数具有int*类型的形参,返回值类型为int。
int (*(*func)[5])(int *p);
func被一个圆括号包含,左边又有一个*,那么func是一个指针,跳出括号,右边是一个[]运算符号,说明func是一个指向数组的指针,现在往左看,左边有一个*号,说明这个数组的元素是指针,再跳出括号,右边又有一个括号,说明这个数组的元素是指向函数的指针。总结一下,就是:func是一个指向数组的指针,这个数组的元素是函数指针,这些指针指向具有int*形参,返回值为int类型的函数。
int (*(*func)(int *p))[5];
func是一个函数指针,这类函数具有int*类型的形参,返回值是指向数组的指针,所指向的数组的元素是具有5个int元素的数组。
要注意有些复杂指针声明是非法的,例如:
int func(void) [5];
func是一个返回值为具有5个int元素的数组的函数。但C语言的函数返回值不能为数组,这是因为如果允许函数返回值为数组,那么接收这个数组的内容的东西,也必须是一个数组,但C语言的数组名是一个右值,它不能作为左值来接收另一个数组,因此函数返回值不能为数组。
int func[5](void);
func是一个具有5个元素的数组,这个数组的元素都是函数。这也是非法的,因为数组的元素除了类型必须一样外,每个元素所占用的内存空间也必须相同,显然函数是无法达到这个要求的,即使函数的类型一样,但函数所占用的空间通常是不相同的。
作为练习,下面列几个复杂指针声明给读者自己来解析。
int (*(*func)[5][6])[7][8];
int (*(*(*func)(int *))[5])(int *);
int (*(*func[7][8][9])(int*))[5];
实际当中,需要声明一个复杂指针时,如果把整个声明写成上面所示的形式,对程序可读性是一大损害。应该用typedef来对声明逐层分解,增强可读性,例如对于声明:
int (*(*func)(int *p))[5];
可以这样分解:
typedef int (*PARA)[5];
typedef PARA (*func)(int *);
这样就容易看得多了。
答案,同时给出用typedef的分解方法:
int (*(*func)[5][6])[7][8];
func是一个指向数组的指针,这类数组的元素是一个具有5X6个int元素的二维数组,而这个二维数组的元素又是一个二维数组。
typedef int (*PARA)[7][8];
typedef PARA (*func)[5][6];
int (*(*(*func)(int *))[5])(int *);
func是一个函数指针,这类函数的返回值是一个指向数组的指针,所指向数组的元素也是函数指针,指向的函数具有int*形参,返回值为int。
typedef int (*PARA1)(int*);
typedef PARA1 (*PARA2)[5];
typedef PARA2 (*func)(int*);
int (*(*func[7][8][9])(int*))[5];
func是一个数组,这个数组的元素是函数指针,这类函数具有int*的形参,返回值是指向数组的指针,所指向的数组的元素是具有5个int元素的数组。
typedef int (*PARA1)[5];
typedef PARA1 (*PARA2)(int*);
typedef PARA2 func[7][8][9];
posted @
2009-03-24 19:32 董超 阅读(43) |
评论 (0) |
编辑
特性,就是为目标元素,可以是数据集、模块、类、属性、方法、甚至函数参数等加入附加信息,类似于注释,但是可以在运行期以反射的方式获得。定制特性主要应用在序列化、编译器指令、设计模式等方面。
多个特性可以应用于同一元素,特性间以逗号隔开,以下表达规则有效:[AttributeUsage][ Flags]、[AttributeUsage, Flags]、[Flags, AttibuteUsageAttribute]、[AttributeUsage(), FlagesAttribute()]
attibute实例,是在编译期进行初始化,而不是运行期。
C#允许以指定的前缀来表示特性所应用的目标元素,建议这样来处理,因为显式处理可以消除可能带来的二义性。例如:
namespace Anytao.net
{
[assembly: MyAttribute(1)] //应用于程序集
[moduel: MyAttribute(2)] //应用于模块
pubic class Attribute_how2do
{
//
}
}
AttributeUsageAttribute特性的作用:Specifies the usage of another attribute class.
往往在定义新特性时应用以前的特性。
[AttributeUsageAttribute(AttributeTarget.All),
AllowMultiple = true,
Inherited = true]
class MyNewAttribute: System.Attribute
{
//
}
- 特性不会影响应用元素的任何功能,只是约定了该元素具有的特质。
- 所有非抽象特性必须具有public访问限制。
- 特性常用于编译器指令,突破#define, #undefine, #if, #endif的限制,而且更加灵活。
- 特性常用于在运行期获得代码注释信息,以附加信息来优化调试。
- 特性可以应用在某些设计模式中,如工厂模式。
- 特性还常用于位标记,非托管函数标记、方法废弃标记等其他方面。
下面是很详细的Attribute应用教程:
Attribute在.net编程中的应用(一)
Attribute在.net编程中的应用(二)
Attribute在.NET编程中的应用(三)
Attribute在.NET编程中的应用(四)
Attribute在.NET编程中的应用(五)
Attribute在.NET编程中的应用(六)
posted @
2009-03-24 08:57 董超 阅读(93) |
评论 (0) |
编辑
“接口不变”,是应该考虑的重要因素。所以,在由接口增加扩展时,应该增加新的接口,而不能更改现有接口。
尽量将接口设计成功能单一的功能块,以.NET Framework为例,IDisposable、IDisposable、IComparable、IEquatable、IEnumerable等都只包含一个公共方法。
抽象类是提取具体类的公因式,而接口是为了将一些不相关的类“杂凑”成一个共同的群体。
当在差异较大的对象间寻求功能上的共性时,使用接口;当在共性较多的对象间寻求功能上的差异时,使用抽象基类。
如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单易行的方法来控制组件版本。通过更新基类,所有继承类都随更改自动更新。另一方面,接口一旦创建就不能更改。如果需要接口的新版本,必须创建一个全新的接口。
如果要设计小而简练的功能块,则使用接口。如果要设计大的功能单元,则使用抽象类。
如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。抽象类允许部分实现类,而接口不包含任何成员的实现。
posted @
2009-03-23 22:42 董超 阅读(13) |
评论 (0) |
编辑
(*) where
用来限制泛型的类型。
where T : struct 可简单理解为只允许值类型(其实是其继承链中必须有值类型)
where T : class 可简单理解为只允许引用类型(其实是其继承链中必须有引用类型)
where T : new() 所有值类型OK,如果是引用类型则必须支持用无参构造函数实例化。(有多个where约束时,new()必须在末尾)
where T : 类名 必须是某个类的子类
where T : 接口名 必须实现了某个接口
多个where限制之间用空格隔开,例如where T : class IDrawable new(),同时满足三种约束
where不支持运算符重载,例如where T : operator +是不允许的,所以不能在函数体内用到+-等运算符,我觉得这是C#应该改进的地方。
(*) 继承泛型基类,实现泛型接口,都必须把类型确定下来
例如:public class BasicMath : IBinaryOperation<int>{...}
(*) 泛型委托
就是连类型都不顾的委托,这玩意有用吗?
class Program
{
public delegate void MyDelegate<T>(T a, T b);
static void ShowSum(int a, int b) { Console.WriteLine(a + b); }
static void ShowDifference(int a, int b) { Console.WriteLine(a - b); }
static void Concatenate(string a, string b) { Console.WriteLine(a + b); }
static void Main()
{
MyDelegate<int> ProceedInteger = ShowSum;
ProceedInteger += ShowDifference;
ProceedInteger(1, 2);
MyDelegate<string> ProceedDouble = Concatenate;
ProceedDouble("a", "b");
}
}
posted @
2009-03-23 11:45 董超 阅读(21) |
评论 (0) |
编辑
(*) 委托
从最简单的例子开始:
namespace ConsoleApplication1
{
class Program
{
// 委托其实就相当于一个类型。这里,类型的名字叫BinaryOp
public delegate int BinaryOp(int x, int y);
static public int Add(int x, int y)
{
return x + y;
}
static void Main()
{
// 创建一个BinaryOp类型的实例,用静态Add方法初始化
BinaryOp d = new BinaryOp(Add);
Console.WriteLine(d(10, 10));
}
}
}
上面是静态方法的委托,下面再来看一个实例方法的委托。
class MyClass
{
private string name;
public MyClass(string name)
{
this.name = name;
}
public void DisplayName()
{
Console.WriteLine("{0}", name);
}
}
class Program
{
// 委托其实就相当于一个类型。这里,类型的名字叫SimpleDelegate
public delegate void SimpleDelegate();
static void Main()
{
MyClass a = new MyClass("A");
MyClass b = new MyClass("B");
// 用实例方法DisplayName初始化
SimpleDelegate d = new SimpleDelegate(a.DisplayName);
d();
d = new SimpleDelegate(b.DisplayName);
d();
}
}
(*) 事件
委托是个类型,而事件是个成员。看下面的代码:
namespace ConsoleApplication1
{
public class SimpleMath
{
public delegate int BinaryOp(int a, int b); // 定义Binary类型
public event BinaryOp BinaryEvent; // 定义BinaryEvent成员
public int Add(int a, int b) { return a + b; }
public int Substract(int a, int b) { return a - b; }
public int Calculate()
{
// Raise the event by using the () operator.
return BinaryEvent(1, 2); // 只能在定义事件的类的内部调用,如果写在外面会编译不过
}
}
class Program
{
static void Main()
{
SimpleMath sm = new SimpleMath();
// sm.BinaryEvent(1, 2); 编译错误!只能在定义事件的类的内部调用
// 下面两种注册方法效果是一样的,相当于注册了两遍,也的确会依序执行两遍
sm.BinaryEvent += new SimpleMath.BinaryOp(sm.Add);
sm.BinaryEvent += sm.Add;
Console.WriteLine(sm.Calculate()); // 结果是3
// 下面两种注册方法效果是一样的,相当于注册了两遍,也的确会依序执行两遍
sm.BinaryEvent += new SimpleMath.BinaryOp(sm.Substract);
sm.BinaryEvent += sm.Substract;
Console.WriteLine(sm.Calculate()); // -1, 只保留最后一次调用的返回值(3,3,-1,-1)
}
}
}
(*) 匿名方法
匿名方法的作用是使代码更简化,方便了程序员。如果没有匿名方法,则定义一套事件监听需要这样:
class Program
{
public delegate void SomeDelegate(); // 定义委托
static void Main()
{
SomeType obj = new SomeType();
obj.SomeEvent += new SomeDelegate(MyEventHandler);
}
// 一般来说,定义的MyEventHandler方法只用来响应事件,只在上面那一处地方使用
public static void MyEventHandler() // 定义委托所调用的方法
{
}
}
上面的代码比较啰嗦,尤其是为委托和它所调用的方法起名字比较费劲我觉得。有了匿名方法以后,仅需要这样:
class Program
{
static void Main()
{
SomeType obj = new SomeType();
obj.SomeEvent += delegate{
// 实现事件处理逻辑
}; // 注意要有分号
}
}
与上一段代码相比,省去了SomeDelegate和MyEventHandler的定义。
posted @
2009-03-19 22:54 董超 阅读(85) |
评论 (0) |
编辑
(*) 接口也可以继承接口,但子接口不提供父接口方法的实现
(*) 当一个类同时继承父类又实现接口,要把接口放在最后面,如class xxx : ParentClass, ISomeInterface
(*) 当同时实现的多个接口中出现同名方法
除了解决同名方法冲突,此段代码同时展示了3种使用接口的方式:
public interface IDraw
{
void Draw();
}
public interface IDraw3D
{
void Draw();
}
public class Image : IDraw, IDraw3D
{
//public void IDraw.Draw() Error! 加任何访问修饰符都会有编译错误
void IDraw.Draw() // 用指定接口名来解决命名冲突,记住这样不能加访问修饰符
{
Console.WriteLine("IDraw");
}
void IDraw3D.Draw()
{
Console.WriteLine("IDraw 3D");
}
}
class Program
{
static void Main(string[] args)
{
Image image = new Image();
// 方法1
try
{
((IDraw)image).Draw();
}
catch (InvalidCastException ex)
{
Console.WriteLine("Wrong type!");
}
// 方法2
IDraw draw = image as IDraw;
if (draw != null)
{
draw.Draw();
}
else
{
Console.WriteLine("Wrong type!");
}
// 方法3
if (image is IDraw)
{
draw.Draw();
}
else
{
Console.WriteLine("Wrong type!");
}
}
}
(*) 自动生成代码
实现接口的类要写不少代码,而且还经常会碰到上面说的语法问题,所以VS提供了一种自动生成代码的方式。
当写好一个类的框架后,如下:
public class Image : IDraw, IDraw3D
{}
把鼠标放在接口上,然后就不说了吧。注意有两种,一种是普通的implement,另一种是explicit implement(即带接口名且没有访问修饰符的)。
(*) 常用接口
(*) IEnumerable
实现IEnumerable接口才能使用foreach,实现IEnumerable接口只需要实现一个GetEnumerator方法。
public interface IEnumable
{
IEnumerator GetEnumerator();
}
注意到GetEnumerator返回一个IEnumerator,这又是一个接口,定义如下:
public interface IEnumerator
{
bool MoveNext(); // 还有下一个则前往下一个并返回ture,否则返回false
object Current { get; } // readonly property,这也是为什么foreach不能改写元素
void Reset(); // 不同于C++的迭代器,reset之后不是指向第一个元素,而是第一个元素之前
}
在实际应用中往往用不着去实现所有这些,因为很多集合例如Array已经实现好了这些接口,
比较常见的一种用法:
IEnumerable在命名空间System.Collections里。
using System.Collections;

class MyItem
{
//
}
class MyList : IEnumerable
{
private MyItem[] myArray;
#region IEnumerable Members
public IEnumerator GetEnumerator()
{
return myArray.GetEnumerator();
}
#endregion
}
也可用yield来实现IEnumerable接口。看代码:
public class DaysOfWeek : IEnumerable
{
string[] m_Days = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" };
#region IEnumerable Members
public IEnumerator GetEnumerator()
{
yield return m_Days[2];
yield return m_Days[4];
yield return m_Days[6];
yield break;
}
#endregion
}
class TestDaysOfTheWeek
{
static void Main()
{
DaysOfWeek week = new DaysOfWeek();
foreach (string day in week)
{
System.Console.WriteLine(day);
}
IEnumerator myEnumerator = week.GetEnumerator();
while (myEnumerator.MoveNext() == true)
{
System.Console.WriteLine(myEnumerator.Current);
}
}
}
yield是很了不起的,它像是一个状态机,记录着迭代器中当前的位置。下面这段关于yield的话是我从别处抄来的,写的似乎很有道理:
1。代码很简洁。其实这里多了一个yield return 语句,由于yield return并不对应多余的il指令。所以编译器就会在编译的时候,生成一个实现Ienumator接口的类.并且自动维护该类的状态.比如movenext,
2. 使用yield return 很容易实现递归调用中的迭代器. 如果以上的问题,不使用yield return的话,可想而知.要么你先把所有的结果暂时放到一个对象集合中. 可是这样就以为着在迭代之前一定要计算号. 要么可能你的movenext 就相当的复杂了. .NET 编译生成的代码其实利用了state machine. 代码量也很大.
类似迭代的调用,比如二叉树遍历 用yield return 就很方便了.另外还有常说的pipeline模式也很方便了.
可是yield return 还是有一些缺陷.
比如:如果有一个参数是ref 或者 out, 那这个state machine就很难去维护状态了. 事实上,yield不支持方法带有ref或者out参数的情况. 还有很多它也不支持,例如unsafe,catch等等,详见MSDN.
(*) ICloneable
只要实现一个Clone方法就行了。这个有点儿像C++中自定义的拷贝构造函数,例如我们不想让多个引用同时指向一个资源,就可以在拷贝构造函数中新申请一个资源。也就是所谓的深拷贝。
需要稍微注意的一点:Clone的signature是
object Clone();
所以在调用Clone的时候经常伴随转型操作,例如:Point p2 = (Point)p1.Clone();
一个深拷贝的例子:
public object Clone()
{
Point newPoint = (Point)this.MemberwiseClone(); // MemberwiseClone是object都有的protected方法
// TODO: 其他需要深拷贝的操作
}
(*) IComparable
和C标准库里的binary_search的钩子函数一个道理。
public interface IComparable
{
int CompareTo(object obj); // this在obj前面(也可以说this比obj小)返回负数,this在obj后面返回正数,相等返回0
}
实现了CompareTo方法之后,就可以用Array的静态方法Array.Sort来排序了。看下面代码:
class Student : IComparable
{
string name;
int score;
public Student(string name, int score)
{
this.name = name;
this.score = score;
}
public override string ToString()
{
return String.Format("{0} {1}", name, score);
}
#region IComparable Members
public int CompareTo(object obj)
{
Student temp = (Student)obj;
// 谁的分高谁在前
if (this.score > temp.score)
{
return -1;
}
else if (this.score < temp.score)
{
return 1;
}
else
{
return 0;
}
}
#endregion
}
class Program
{
static void Main()
{
Student stu1 = new Student("Bill", 75);
Student stu2 = new Student("Steve", 85);
//Console.WriteLine(stu1 > stu2); 实现了CompareTo函数也不能直接用>和<来比较
Console.WriteLine(stu1.CompareTo(stu2)); // 若stu1比stu2靠后,则返回正数
Student[] group = new Student[2];
group[0] = stu1;
group[1] = stu2;
Array.Sort(group);
foreach (Student stu in group)
{
Console.WriteLine(stu);
}
}
}
Array.Sort还提供了重载,即允许把排序准则当参数传入。
interface IComparer
{
int Compare(object o1, object o2);
}
//实现IComparer接口的类
public class NameComparer : IComparer
{
// 按名字排序
}
//调用
Array.Sort(group, new NameComparer);
posted @
2009-03-17 21:53 董超 阅读(53) |
评论 (0) |
编辑
默认情况下,GC自动回收的资源只有托管堆上的内存,其他资源如数据库连接、流等等都不在GC的管理范围之内,这些资源统称为非托管资源。
当不使用托管资源的时候,默认的析构函数就可以搞定一切。只有在需要手工释放非托管资源的时候才应该(不是必须)重写析构函数。
有一点和C++不一样的地方,在C#里只有类才在托管堆上分配内存,也只有类才有析构函数,struct是不能有析构函数的。所以尽量不要在struct里分配非托管资源,因为struct不得不失去析构函数这一层保障。
GC在工作的时候,会先停下该进程的所有线程。所以对于托管资源,没事的时候不要自己强制进行垃圾回收,会影响性能。只有当很特殊的情况,例如你刚刚分配了一大堆内存,而又马上要进入一个不想被垃圾回收打断的过程,这时候才值得手工垃圾回收。
手工垃圾回收的代码:
GC.Collect();
GC.WaitForPendingFinalizers(); //Suspends the current thread until the thread that is processing the queue of finalizers has emptied that queue.
有三代:0,1,2
0代最有可能被回收
1代次之
2代最不可能被回收
年龄越大的被回收的可能性越小。
当垃圾回收被某个事件触发的时候,会先清理0代,把能清理的都清理了,不能清理的挪到1代。若把0代的清理完了内存还不够用再清理1代,把1代能清理的都清理了,还有用的挪到2代。2代就到头了,不能往后挪了。
托管资源交给GC就好,非托管资源则必须亲自写代码回收。因此对于非托管资源,一般要写一个Dispose方法来实现IDisposable,这样就可以放在using块中自动调用Dispose,也可以显示调用Dispose函数来进行清理。除此之外,最好在析构函数中也释放资源,一旦忘记调用Dispose函数也好有个补救。但不能只依赖析构函数,因为GC什么时候开始进行垃圾回收还不一定,对象处于第几代也不能事先确定,所以等到GC启动了可能某些稀缺资源早就耗尽了。
下面是MSDN上的best practice,非常有参考价值:
实现IDispose:http://msdn.microsoft.com/zh-cn/library/system.idisposable.dispose.aspx
有继承关系的类的Dispose:
// Design pattern for a base class.
public class Base : IDisposable
{
private bool disposed = false;
//Implement IDisposable.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // Prevent finalization code executing
}
protected virtual void Dispose(bool disposing)
{
if (disposed)
{
return;
}
if (disposing)
{
// TODO: Release managed resources.
}
// TODO: Release unmanaged resources.
}
// Use C# destructor syntax for finalization code.
~Base()
{
// Simply call Dispose(false).
Dispose(false);
}
}
// Design pattern for a derived class.
public class Derived : Base
{
protected override void Dispose(bool disposing)
{
if (disposed)
{
return;
}
if (disposing)
{
// TODO: Release managed resources.
}
// TODO: Release unmanaged resources.
// Call Dispose on your base class.
base.Dispose(disposing);
}
// The derived class does not have a Finalize method
// or a Dispose method without parameters because it inherits
// them from the base class.
}
上面一段代码中有一点我还不太明白,即为什么析构函数用Dispose(false),而不敢去释放托管资源,在MSDN上找到的解释是:
Dispose(bool disposing) executes in two distinct scenarios. If disposing equals true, the method has been called directly or indirectly by a user's code and managed and unmanaged resources can be disposed. If disposing equals false, the method has been called by the runtime from inside the finalizer and only unmanaged resources can be disposed. When an object is executing its finalization code, it should not reference other objects, because finalizers do not execute in any particular order. If an executing finalizer references another object that has already been finalized, the executing finalizer will fail.
它说在析构函数中引用的某些对象有可能已经被释放了,所以再去释放它们是不对的。似乎有点儿道理,但问题是,如果析构函数不被调用,那么包含在这个类中的其他对象的引用怎么可能被析构了呢?
posted @
2009-03-16 22:49 董超 阅读(70) |
评论 (0) |
编辑