其实许多朋友已经在回复中发现问题所在了,其中最早指出错误的是狼Robot同学,他说:

每个T都会使用一个新的连接。

泛型类中的静态变量会因为T的不同而产生不同的值,也就是说每个T所访问的静态变量都是独立的。

正是这个原因,导致UserRepository和ArticleRepository,虽然似乎都继承了Repository<T>类,但是因为使用了不同的T类型,所以实际上它们是不同的类,而它们的ConnectionKey值是不同的。使用不同的ConnectionKey,就无法从ResourceManager中获得同一个Connection对象了。以下的代码可以很轻易地证明这一点。

public static class MyClass<T>
{public static readonly Guid Key = Guid.NewGuid();
}class Program
{static void Main(string[] args){Console.WriteLine("int: " + MyClass<int>.Key);Console.WriteLine("string: " + MyClass<string>.Key);Console.ReadLine();}
}

由于MyClass<int>和MyClass<string>是不同的类,因此它们的Key也是分离的,值也不同(Guid.NewGuid()了两次)。因此,在泛型类中定义静态字段的时候一定要注意:不同泛型参数生成的具体类(无论是值类型还是引用类型),它们的静态字段是独立的。这点说起来简单,但是有时候不太容易意识到。例如,我之所以犯这个错误,正是因为原本Repository类是非泛型的,而后面由于某些原因才将其改为泛型。这样的错误使用单元测试也很难检查出来,非常隐蔽。

不过解决方案也非常简单,例如随意给出一个具体的Guid,而不是每次都使用Guid.NewGuid生成新的值:

public abstract class Repository<T>
{private readonly static Guid ConnectionKey = new Guid("a18b2f49-cafc-43e3-a49d-3fac91701394");
}

这样,虽然UserRepostory和ArticleReposityr的ConnectionKey还是不同的Guid对象,但是它们的“值”是相同的(也就是说GetHashCode相同,Equals返回true),对字典来说它们是相同的“键”。当然,还有其他解决方案,例如把ConnectionKey放到其它非泛型的类中去即可。

有些朋友还提出了其他的观点。例如,ResourceManager是不同的实例,怎么做到“保留Connection对象”呢?其实只需要它们都基于一个合适的数据容器就可以了,比如都基于HttpContext.Current。这方面的例子很多,比如不同的Connection对象都是访问同一个数据库的。因此,这里不是问题。

还有,有朋友认为共享Connection对象的做法不好。其实这也是没有关系的,因为这里“共享”的范围只是“单个请求”。对于ASP.NET请求来说,这些操作都是同步的,因此不会产生线程安全的问题。而一个请求的时间很短,因此Connection的生命周期也不长。这样的实践很多,例如NHiberante推荐为每个请求分配一个唯一的ISession对象(Sharp Architecture就是怎么做的)——这就相当于一个Connection——不过我不喜欢,因此我使用的做法是为单个请求按需创建多个Session,但是共享一个Connection对象。此外,共享Connection对象还有其他一些好处,例如不会引发需要MSDTC的分布式事务。

这个问题已经解决了。但是上文的评论中还有其他一些讨论。例如,您知道为什么下面的代码中,两个时间是相同的吗?

public static class MyClass<T>
{public static readonly DateTime Time = DateTime.Now;
}class Program
{static void Main(string[] args){Console.WriteLine("int: " + MyClass<int>.Time);Thread.Sleep(3000);Console.WriteLine("string: " + MyClass<string>.Time);Console.ReadLine();}
}

它们输出的结果是:

int: 2009/9/8 15:30:06
string: 2009/9/8 15:30:06

这和我们的理解好像不同,因为当我们访问MyClass<string>的时候,应该比MyClass<int>要晚3秒钟,但为什么时间是相同的呢?那么我们把测试代码换一种写法,会更清楚一些:

public static class MyClass<T>
{public static readonly DateTime Time = GetNow();private static DateTime GetNow(){Console.WriteLine("GetNow execute!");return DateTime.Now;}
}class Program
{static void Main(string[] args){Console.WriteLine("Main execute!");Console.WriteLine("int: " + MyClass<int>.Time);Thread.Sleep(3000);Console.WriteLine("string: " + MyClass<string>.Time);Console.ReadLine();}
}

我们增加了会输出一些内容的GetNow静态方法,Main方法的开头也打印出一些内容。这段代码输出如下:

GetNow execute!
GetNow execute!
Main execute!
int: 2009/9/8 15:34:31
string: 2009/9/8 15:34:31

可以发现,在Main方法执行之前,MyClass<int>和MyClass<string>的GetNow就被调用了。因此,它们的Time字段是相同的。不过,如果我们在MyClass<>中增加一个空的静态构造函数,结果就会有所不同:

public static class MyClass<T>
{public static readonly DateTime Time = GetNow();private static DateTime GetNow(){Console.WriteLine("GetNow execute!");return DateTime.Now;}static MyClass() { }
}

输出如下:

Main execute!
GetNow execute!
int: 2009/9/8 15:40:12
GetNow execute!
string: 2009/9/8 15:40:15

由于GetNow方法只在“第一次”用到MyClass<int>和MyClass<string>时执行,因此获得的时间是不同的。不过,为什么加入了静态构造函数之后,Time字段的初始化时机就有所改变呢?那是因为IL中beforefieldinit修饰在作怪。关于这一点,许多书中都有提及。园子中的Artech同学对这个问题也有所分析。

在目前的情况下,泛型类这一性质给我们造成了一定的麻烦。但是,只要我们使用得当,它也可以在某些场景下简化开发。因此,最后请大家和我一起在心中默念:信脑袋,得永生,信脑袋,得永生……

from: http://blog.zhaojie.me/2009/09/i-made-a-mistake-can-you-figure-it-out-answer.html

我犯了一个错误,您能指出吗?(结论)相关推荐

  1. 我犯了一个错误,您能指出吗?

    这是我最近在项目中犯的一个错误,您能指出吗? 这个项目在数据访问方面使用了传统的Repository模式.为此,我定义了一个Repository基类,可以让每个不同的Repository继承它: pu ...

  2. 农民约翰是一个惊人的会计_我的朋友约翰在CSS Grid中犯了一个错误。 不要像约翰-这样做。

    农民约翰是一个惊人的会计 It had been two years and John had no job. 已经两年了,约翰没有工作. John was a smart 20-something ...

  3. 逆序枚举时常犯的一个错误

    一 写在开头 1.1 本节内容 分享一个刚刚调通的BUG的过程与结果. 二 排错过程与原因分析 今天在写代码的过程中发现了一个很有意思的BUG,触发该BUG的原因很简单,而且我之前也遇到过.看来这个B ...

  4. 今天犯的一个错误,导致method GET must not have a request body

    事件经过: 1.在本地机器运行完全正常的程序,手动人工发包到测试环境上,后台日志频频报method GET must not have a request body. 2.使用postman发送pos ...

  5. Python import容易犯的一个错误

    有时,我们需要手动添加一些依赖 b.py import sys sys.path.insert(0,"haha")#引用haha目录下的a文件 当使用时 import a impo ...

  6. Python 程序员最常犯的十个错误

    常见错误1:错误地将表达式作为函数的默认参数 在Python中,我们可以为函数的某个参数设置默认值,使该参数成为可选参数.虽然这是一个很好的语言特性,但是当默认值是可变类型时,也会导致一些令人困惑的情 ...

  7. Python 程序员最常犯的十个错误,作为小白的你是不是也经常犯?

    常见错误1:错误地将表达式作为函数的默认参数 在Python中,我们可以为函数的某个参数设置默认值,使该参数成为可选参数.虽然这是一个很好的语言特性,但是当默认值是可变类型时,也会导致一些令人困惑的情 ...

  8. Python程序员常犯的十个错误

    不管是在学习还是工作过程中,人都会犯错.虽然Python的语法简单.灵活,但也一样存在一些不小的坑,一不小心,不管是初学者还是资深Python程序员都有可能会栽跟头. 常见错误1:错误地将表达式作为函 ...

  9. 我犯了一个低级的C++的逻辑错误

    自己犯了一个低级错误.留作警示. #include <iostream> #include <fstream> #include <vector> #include ...

最新文章

  1. 鸿蒙系统华为什么手机你能用,【图片】华为鸿蒙系统的厉害之处在于 你可能非用不可 !【手机吧】_百度贴吧...
  2. 黑夜主权个人团队html源码 简单修改即可使用
  3. 09 进程池的异步方法
  4. 封装的ini文件类。保存为unicode的。解决delphi xe的TiniFile保存后不为unicode的问题...
  5. Tecplot 360 EX 2020 R1中文版
  6. ubuntu安装rabbitvcs
  7. ios 图片加载内存尺寸_iOS图片内存优化
  8. CentOS 7.9.2009查看本机IP地址
  9. linux中 ~ / ./分别表示什么目录
  10. 在我的网站   Φ十三月网Φ  开站前的宣传,当我是炒作,造势都可以,但是我们要的是有内涵。
  11. 旅人随笔[01] 何为开源?
  12. jellyfin 字幕方框问题
  13. 2018-07-03 根据Excel后缀名获取WorkBook
  14. 我打算写一个《程序员的成长课》
  15. Unloaded branch node detected. “loadOptions“ prop is required to load its children
  16. 生成微信小程序二维码,可跳转到小程序指定页面。
  17. 2014年880个合集Android_实例子源代码文件下载地址合集-2014
  18. C语言与机器人 plc的关系,工业机器人、PLC与自动化三者之间有什么关系
  19. SQLite3源码学习(18) 互斥锁
  20. 标签软件如何批量打印多排条码标签

热门文章

  1. Spring-AOP 使用@AspectJ
  2. JVM-01Java内存区域与内存溢出异常(上)【运行时区域数据】
  3. python如何只保留数字_雷军透露小米或只保留数字、MIX和CC系列 官宣视频首度曝光小米CC...
  4. 图形颜色填充_Processing-2-基本图形绘制
  5. 2021-02-28 LQG控制的主动悬架1/2车垂向动力学模型
  6. python matplotlib 画图神器
  7. 抚州虚拟服务器,南通虚拟主机_南通云虚机_南通主机申请_南通网站空间_爱名网(www.22.cn)...
  8. android ip地址扫描,Android:手机扫描局域网所有ip,并进行socket通讯
  9. 计算机的优势和劣势_100亿倍,中国量子计算机完胜美国,向中方科学家致敬
  10. 超过200m文件发送_喜大普奔!微信官宣:最高支持200M高清视频、图片文件!