MemberwiseClone 方法创建一个浅表副本,具体来说就是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象。
下面的代码就是演示这个问题:
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace CloneDemo
{
[Serializable]
class DemoClass
{
public int i = 0;
public int[] iArr = { 1, 2, 3 };
public DemoClass Clone1()
{
return this.MemberwiseClone() as DemoClass;
}
public DemoClass Clone2()
{
MemoryStream stream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, this);
stream.Position = 0;
return formatter.Deserialize(stream) as DemoClass;
}
}
class Program
{
static void Main(string[] args)
{
DemoClass a = new DemoClass();
a.i = 10;
a.iArr = new int[] { 8, 9, 10 };
DemoClass b = a.Clone1();
DemoClass c = a.Clone2();
// 更改 a 对象的iArr[0], 导致 b 对象的iArr[0] 也发生了变化
a.iArr[0] = 88;
Console.WriteLine("MemberwiseClone");
Console.WriteLine(b.i);
foreach (var item in b.iArr)
{
Console.WriteLine(item);
}
Console.WriteLine("Clone2");
Console.WriteLine(c.i);
foreach (var item in c.iArr)
{
Console.WriteLine(item);
}
Console.ReadLine();
}
}
}
Singleton模式是最简单的模式,比较汗颜的是自己一直以来使用的是单线程的Singleton模式,最近在听了李建忠老师的模式讲座录像后,才发现自己一直没注意到这点。这个录像讲座在后面给出了链接地址: C#面向对象设计模式纵横谈(2):Singleton 单件(创建型模式)
下面内容整理自李建忠老师的讲课内容:
单线程的Singleton模式
public class Singleton
{
private static Singleton instance;
private Singleton() { }
public static Singleton Instance
{
get
{
if (instance == null)
instance = new Singleton();
return instance;
}
}
}
要点:
- Singleton模式中的实例构造器可以设置为protected以允许子类派生。
- Singleton模式一般不要支持ICloneable接口,因为这可能会导致多个对象实例,与Singleton模式的初衷违背。
- Singleton模式一般不要支持序列化,因为这也有可能导致多个对象实例,同样跟Singleton模式的初衷违背。
- Singleton模式只考虑到了对象创建的管理,并没有考虑对象销毁的管理。就支持垃圾回收的平台和对象的开销来讲,我们一般没必要对其销毁进行特殊的管理。
- 上述代码不支持多线程环境,上述代码在多线程下,仍然有可能得到Singleton类的多个实例。
多线程的Singleton模式
public class Singleton
{
private static volatile Singleton instance = null;
private static object lockHelper = new object();
private Singleton() { }
public static Singleton Instance
{
get
{
if (instance == null)
{
lock (lockHelper)
{
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
}
代码说明:
- volatile C#关键字作用,简单来说,编译器编译我们的代码时候,会对代码作一些优化,进而对代码进行了微调,使用volatile关键字就可以避免这个微调。继而严格意义上保证不会产生多线程。更详细的关于volatile 的说明,请看参考资料。
- 双检查加锁模式。在lock之外和之内,我们做了instance是否为空的检查。这叫双检查。因为同步控制的时间太长了。双检查能够最高效地实现多线程安全的访问。
使用.net特有的支持多线程的单件模式代码
public sealed class Singleton
{
public static readonly Singleton Instance = new Singleton();
private Singleton() { }
}
代码说明:
- sealed 修饰符表示该类是密封类,不能被继承。你可以按需修改。
- 这里readonly关键字只是不希望客户程序将Instance字段设置为null等不合理的值。
- *** static *** = new Singleton(); 是使用了内联初始化技术,这部分初始化其实是在static Singleton()中执行的。即上面的代码相当于:
public sealed class Singleton
{
public static readonly Singleton Instance;
static Singleton()
{
Instance = new Singleton();
}
private Singleton() { }
}
上述代码在编译时,会使用一个名为beforefieldinit元数据标志。此标志使得运行库能够在任何时候执行类型构造函数方法,只要该方法在第一次访问该类型的静态字段之前执行即可。换句话说,beforefieldinit 为运行库提供了一个执行主动优化的许可。如果没有 beforefieldinit,运行库就必须在某个精确时间运行类型构造函数,即,恰好在第一次访问该类型的静态或实例字段和方法之前。
静态构造器自身就可以保证,多线程情况下,系统就可以保证只有一个执行。
缺点:
- 不支持参数化单件构造器。即静态构造器不支持参数,就导致我们无法利用静态构造器实现传参数的单件。
参考资料:
讲讲volatile的作用
http://blog.21ic.com/user1/2949/archives/2007/35599.html
Implementing the Singleton Pattern in C#
http://www.yoda.arachsys.com/csharp/singleton.html
初始化内联引用类型静态字段
http://msdn.microsoft.com/zh-cn/library/ms182275.aspx
通过七个关键编程技巧得益于静态内容
http://www.microsoft.com/china/MSDN/library/enterprisedevelopment/softwaredev/us0501StaticsinNET.mspx?mfr=true
模式设计c#--创建型--Singleton
http://www.cppblog.com/mzty/archive/2006/01/03/2384.html
今天在看 EnterpriseLibrary 源文件时,看到如下的代码,这个代码可以比较经典的解释Thread.Sleep(0)的用途。代码如下:
Hashtable inMemoryCache;
CacheItem cacheItemBeforeLock = null;
// ..... 一些其他代码
// 通过循环,以获得 cacheItemBeforeLock 的控制权。
bool lockWasSuccessful;
do
{
lock (inMemoryCache.SyncRoot)
{
// .....
lockWasSuccessful = Monitor.TryEnter(cacheItemBeforeLock);
}
if (lockWasSuccessful == false)
{
Thread.Sleep(0);
}
} while (lockWasSuccessful == false);
try
{
// 对 cacheItemBeforeLock 作一些操作
// .....
}
finally
{
Monitor.Exit(cacheItemBeforeLock);
}
Thread.Sleep(0); 线程挂起0毫秒。表面看来这个代码是没啥意义的。
其实不然,挂起0毫秒的意义不在于0毫秒,而在于挂起2个字,线程挂起,其他线程就有机会优先执行。
上面的代码中,在Thread.Sleep(0);之前,这个线程跟其他线程争抢cacheItemBeforeLock的情况,就会因为这个挂起而得到解决。
参考:
关于Thread.Sleep(0)
http://www.cnblogs.com/zzgfly/archive/2007/07/15/818426.html
Thread.Sleep 方法 (Int32)
http://msdn.microsoft.com/zh-cn/library/d00bd51t(VS.80).aspx
设置具体数据库启动Service Broker服务,如下图:
我这里试例数据库的名字为“ghj_Demo”,修改 Broker Enabled 属性为 true。
你也可以用SQL 语句来修改,修改的SQL语句如下:
ALTER DATABASE ghj_Demo SET ENABLE_BROKER
确保你将使用的数据库帐户具有必需的权限
你在后面连接这个数据库的帐户,要确保对这个数据库具有 SUBSCRIBE、 QUERY 、NOTIFICATIONS 的权限。
下面就是一个简单的代码例子,来演示查询通知。这里用到一个我自己建立的表:
这个表结构如下:
CREATE TABLE [dbo].[User](
[UserName] [nvarchar](20) NOT NULL,
[Email] [nvarchar](50) NULL
) ON [PRIMARY]
GO
演示的控制台代码如下:
using System;
using System.Data;
using System.Data.SqlClient;
namespace Demo
{
class Program
{
public static string connectionstring = "Data Source=.;Initial Catalog=ghj_Demo;Integrated Security=True";
public void DoDependency()
{
using (SqlConnection conn = new SqlConnection(connectionstring))
{
// sql is a local procedure that returns
// a paramaterized SQL string. You might want
// to use a stored procedure in your application.
string sql = "select [UserName] ,[Email] from dbo.[User]";
SqlCommand cmd = new SqlCommand(sql, conn);
// Ensure the command object does not have a notification object.
cmd.Notification = null;
//Notification specific code
SqlDependency dep = new SqlDependency(cmd);
dep.OnChange += new OnChangeEventHandler(TestEvent);
conn.Open();
SqlDataReader r = cmd.ExecuteReader();
//Read the data here and close the reader
r.Close();
conn.Close();
Console.WriteLine("DataReader Read...");
}
}
void TestEvent(Object o, SqlNotificationEventArgs args)
{
// 注意:
// 如果Server端在很短的时间内发生了大量的改动(比如用了一个循环Update了好多行),
// OnChanged必须能迅速处理事件,否则它只会被触发一次。这个不是缺陷,
// 因为一般OnChanged事件处理函数内都要执行类似刷新缓存的操作,它只触发一次,
// 不会影响程序逻辑,却能提高程序性能。
Console.WriteLine("======================");
Console.WriteLine("Event Recd");
Console.WriteLine("Info:" + args.Info);
Console.WriteLine("Source:" + args.Source);
Console.WriteLine("Type:" + args.Type);
}
static void Main(string[] args)
{
// In order to use the callback feature of the
// SqlDependency, the application must have
// the SqlClientPermission permission.
try
{
SqlClientPermission perm = new SqlClientPermission(
System.Security.Permissions.PermissionState.Unrestricted);
perm.Demand();
}
catch
{
throw new ApplicationException("No permission");
}
try
{
SqlDependency.Stop(connectionstring);
//Start the listener infrastructure on the client
SqlDependency.Start(connectionstring);
Program q = new Program();
q.DoDependency();
Console.WriteLine("Wait for Notification Event...");
Console.Read();
}
finally
{
//Optional step to clean up dependency else it will fallback to automatic cleanup
SqlDependency.Stop(connectionstring);
}
}
}
}
一些注意事项:摘自MSDN文档
使用查询通知功能的应用程序需要考虑下列特殊注意事项。
|
注意事项
|
说明
|
|
SQL Server 的服务帐户
|
对于使用本地系统帐户作为服务帐户的 SQL Server 实例,应用程序不会从其接收通知。
|
|
接收通知
|
无法在运行 Windows 95 或 Windows 98 的计算机上接收通知。
|
|
查询通知和事务
|
如果在某一事务内进行了多项影响具有已注册通知请求的一组数据的修改,则仅会发送单个通知事件。
|
|
快速更新和查询通知
|
使用查询通知的应用程序必须考虑到立即出现通知的情况。服务器上的数据更改时,通知消息将发送到相应的服务中介程序队列。 应用程序需要注册才能接收其他通知。 因此,如果多个应用程序快速更新某个数据集,应用程序在缓存刷新后,立即可以接收通知,检索数据,然后获取另一个更新通知。 编写使用查询通知的应用程序时必须考虑到此情况。 如果应用程序使用不断更新的数据,则可能更适合使用另一种数据缓存策略。
|
|
设置选项设置
|
在通知请求下执行 SELECT 语句时,提交请求的连接必须设置以下选项:
· ANSI_NULLS ON
· ANSI_PADDING ON
· ANSI_WARNINGS ON
· CONCAT_NULL_YIELDS_NULL ON
· QUOTED_IDENTIFIER ON
· NUMERIC_ROUNDABORT OFF
· ARITHABORT ON
|
编写通知查询语句的约束
您可以为 SELECT 和 EXECUTE 语句设置通知。 使用 EXECUTE 语句时,SQL Server 会为执行的命令而不是 EXECUTE 语句本身注册通知。 该命令必须满足 SELECT 语句的要求和限制。 当注册通知的命令包含多个语句时,数据库引擎会为批处理中的每个语句创建一个通知。
对满足以下要求的 SELECT 语句支持查询通知:
-
SELECT 语句中的提取的列必须显式声明,且表名称必须用由两部分组成的名称进行限定。 请注意,这意味着语句中引用的所有表都必须位于同一个数据库中。
-
语句不能使用星号 (*) 或 table_name.* 语法来指定列。
-
语句不能使用未命名的列或重复的列名称。
-
语句必须引用一个基表。
-
SELECT 语句中的提取的列不能包含聚合表达式,除非该语句使用 GROUP BY 表达式。 在提供 GROUP BY 表达式的情况下,选择列表可以包含聚合函数 COUNT_BIG() 或 SUM()。 不过,不能为可以为 null 的列指定 SUM()。 语句不能指定 HAVING、CUBE 或 ROLLUP。
-
SELECT 语句中的提取的列用作简单表达式时不能出现多次。
-
语句不能包含 PIVOT 或 UNPIVOT 运算符。
-
语句不能包含 INTERSECT 或 EXCEPT 运算符。
-
语句不能引用视图。
-
语句不能包含以下任一项: DISTINCT、COMPUTE、COMPUTE BY 或 INTO。
-
语句不能引用服务器全局变量 (@@variable_name)。
-
语句不能引用派生表、临时表或表变量。
-
语句不能引用其他数据库或服务器中的表或视图。
-
语句不能包含子查询、外部联接或自联接。
-
语句不能引用大型对象类型: text、ntext 和 image。
-
语句不能使用 CONTAINS 或 FREETEXT 全文谓词。
-
语句不能使用行集合函数,包括 OPENQUERY 和 OPENROWSET。
-
语句不能使用以下任一集合函数: AVG、COUNT(*)、MAX、MIN、STDEV、STDEVP、VAR 或 VARP。
-
语句不能使用任何不确定性函数,包括排名和开窗函数。
-
语句不能包含用户定义的聚合。
-
语句不能引用系统表或视图,包括目录视图和动态管理视图。
-
语句不能包含 FOR BROWSE 信息。
-
语句不能引用队列。
-
语句不能包含无法更改或无法返回结果的条件语句(例如 WHERE 1=0)。
sunmast 对查询通知的注意事情也有很多有价值的整理:
使用SQL Server 2005 Query Notification的几个注意事项
http://blog.joycode.com/sunmast/archive/2006/08/11/sql_2005_query_notification_comments_79814.aspx
参考资料:
剖析SQL Server 2005查询通知之基础
http://www.allwiki.com/wiki/%E5%89%96%E6%9E%90SQL_Server_2005%E6%9F%A5%E8%AF%A2%E9%80%9A%E7%9F%A5%E4%B9%8B%E5%9F%BA%E7%A1%80
Using SqlDependency for data change events
http://www.codeproject.com/KB/database/chatter.aspx
SQL Server 2005 Service Broker 初探
http://msdn.microsoft.com/zh-cn/library/ms345108.aspx
SQL Server 2005数据库开发详解
http://book.csdn.net/bookfiles/24/10024713.shtml
C# Windows 应用程序中实现 SQL Server 2005 查询通知
http://support.microsoft.com/kb/555893/zh-cn
SqlDependency changes for RTM [Sushil Chordia]
http://blogs.msdn.com/dataaccess/archive/2005/09/27/474447.aspx
.NET 2.0 SqlDependency快速上手指南
http://www.cnblogs.com/Xrinehart/archive/2006/07/27/461106.html
在 Windows 应用程序中使用 SqlDependency
http://msdn.microsoft.com/zh-cn/library/a52dhwx7(VS.80).aspx
Using SqlDependency in an ASP.NET Application
http://msdn.microsoft.com/en-us/library/9dz445ks(VS.80).aspx
Minimum Database Permissions Required for SqlDependency
http://www.codeproject.com/KB/database/SqlDependencyPermissions.aspx
使用SQL Server 2005 Query Notification的几个注意事项
http://blog.joycode.com/sunmast/archive/2006/08/11/sql_2005_query_notification_comments_79814.aspx