C#泛型设计的一个小陷阱.
距离上次发表博客已经有几年了. 对于没能坚持更新博客,实在是感觉到甚是惭愧.
闲言少叙, 直接切入主题.
背景
最近一直在对于公司一个网络通信服务程序使用.net core 进行重构.重构的目的有两个:一是让程序能够跨平台运行. 二是优化程序代码结构是程序的可维护性有所提升. 重构的过程主要由我来设计底层的架构. 在这个过程中,由于我对C# 泛型的理解还不够深入,所以在这个方面我就犯了个错误. 希望本文能把我犯的这个错误阐述清楚, 如果能帮助园里其他朋友避免这个问题当然是最好的了.
早前的设计
先用一张图来描述早前的代码结构

Singleton<T>:是一个单例的基类, 用来实现单例模式.
Base<T>:则是一个基础类,它有一些静态的属性和方法(例如访问Redis,kafka,数据库等). 这些属性和方法提供给 Child1 和 Child2 去使用.
Child1 和Child2: 相当于不同模块的业务逻辑实现.
我期望的结果是Base<T>里面的静态成员在整个程序运行期间只有一份.
代码的实现
Singleton
public abstract class Singleton<T> where T : new()
{
/// <summary>
/// 锁定对象
/// </summary>
private static readonly object locker = new object();
/// <summary>
/// T 的实例
/// </summary>
static T instance = default(T);
/// <summary>
/// T 的实例
/// </summary>
public static T Instance
{
get
{
if (null == instance)
{
lock (locker)
{
if (null == instance)
{
instance = new T();
}
}
}
return instance;
}
}
}
Base
public class Base<T> : Singleton<T> where T : new()
{
protected static object Object { set; get; } static Base()
{
Object = new object();
}
}
Child1 和Child2
public class Child1 : Base<Child1>
{
} public class Child2 : Base<Child2>
{
}
我以为 Base的静态构造函数只会执行一次. 可是当我在程序里使用 Child1.Instance 和 Child2.Instance 时发现, Base的静态构造函数被执行了2次. 那么Child1.Instance的Object和Child2.Instance的Object对象一定不是同一个.
那么问题出现在什么地方了呢? 答案其实挺简单的:系统认为 Base<Child1> 和 Base<Child2>并不相同. 相当于在系统里定义了Base_Child1 和Base_Child2两个类. 如果我们这么理解这个问题 ,那么Base的静态构造函数被执行了2次就不难理解了.(我觉得我已经把这个问题的成因描述清楚了,如果您没理解,欢迎在下面评论.)
如果要达到我设计的目标应该怎么做呢?
修正的设计
还是先上类图.

Base:
public class Base
{
protected static object Object { set; get; } static Base()
{
Object = new object();
}
}
Singleton:
public abstract class Singleton<T>: Base where T : new()
{
/// <summary>
/// 锁定对象
/// </summary>
private static readonly object locker = new object();
/// <summary>
/// T 的实例
/// </summary>
static T instance = default(T);
/// <summary>
/// T 的实例
/// </summary>
public static T Instance
{
get
{
if (null == instance)
{
lock (locker)
{
if (null == instance)
{
instance = new T();
}
}
}
return instance;
}
}
}
Child1 和Child2:
public class Child1 : Singleton<Child1>
{
} public class Child2 : Singleton<Child2>
{
}
由Singleton 来继承Base.然后Child1 和Child2来继承Singleton. 这样问题就都解决了.
C#泛型设计的一个小陷阱.的更多相关文章
- [需求设计]从一个小需求感受Redis的独特魅力
分享一个简单的小需求应该怎么设计实现以及有关Redis的使用 Redis在实际应用中使用的非常广泛,本篇文章就从一个简单的需求说起,为你讲述一个需求是如何从头到尾开始做的,又是如何一步步完善的.之前写 ...
- 警惕!Unity3D中UnityEngine.Object的一个小陷阱
先看看如下C#的脚本代码: 猜猜控制台打出来的是什么? In the bool parameter function, value info is: True 肯定出乎很多人的意料吧? transf ...
- Thread与Runnable的一个小陷阱
Java里面运行一个线程可以通过继承Thread的方式,也可以通过实现Runnable的接口来实现,那么两者能不能混用呢,比如以下的例子: public class JavaTest extends ...
- ios调用dismissViewController的一个小陷阱
我们的APP从启动到进入主页面.是通过presentViewController构造了一个ViewController序列,类似于首页 -> 登陆页 -> 启动载入页 -> 主页面 ...
- QString 转换成 wchar 的一个小陷阱
QString::toWCharArray(wchar_t * array) 其中 wchar_t * array 除了要分配内存之外,必须用 wmemset 初始化. 环境是 Visual Stud ...
- 一个小栗子聊聊JAVA泛型基础
背景 周五本该是愉快的,可是今天花了一个早上查问题,为什么要花一个早上?我把原因总结为两点: 日志信息严重丢失,茫茫代码毫无头绪. 对泛型的认识不够,导致代码出现了BUG. 第一个原因可以通过以后编码 ...
- T-SQL中的一些小陷阱
1,当心ISNULL函数对你的逻辑引起BUG 有人喜欢或者习惯于(并不代表我推荐,甚至这种写法没有任何好处)用ISNULL处理变量这种方式写查询 比如:select * from TestISNULL ...
- python小练习1:设计这样一个函数,在桌面的文件夹上创建10个文本,以数字给它们命名。
python小练习1:设计这样一个函数,在桌面的文件夹上创建10个文本,以数字给它们命名. 使用for循环即可实现: for name in range(1,11): desktop_path='C: ...
- Go的List操作上的一个小“坑”
转自http://sharecore.net/blog/2014/01/09/the-trap-in-golang-list/ 一直想不清楚一个问题,简单设计的东西到底是“坑多”还是“坑少”呢? 复杂 ...
随机推荐
- SparkSQL大数据实战:揭开Join的神秘面纱
本文来自 网易云社区 . Join操作是数据库和大数据计算中的高级特性,大多数场景都需要进行复杂的Join操作,本文从原理层面介绍了SparkSQL支持的常见Join算法及其适用场景. Join背景介 ...
- python中的内置函数(一), lambda, filter, map
https://www.processon.com/view/link/5c10da0ce4b099ae3e137bf6 1.内置函数 内置函数就是python中提供的,可以直接拿来用的函数,比如pr ...
- SUBSET
DESCRIPTION:一开始你有一个空集,集合可以出现重复元素,然后有Q 个操作1. add s在集合中加入数字s.2. del s在集合中删除数字s.保证s 存在3. cnt s查询满足a& ...
- jquery中通过添加readonly或者disabled属性实现行为禁止 / 去除某个属性的方法
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- ZooKeeper学习1---简单介绍
一.分布式协调技术 在给大家介绍ZooKeeper之前先来给大家介绍一种技术——分布式协调技术.那么什么是分布式协调技术?那么我来告诉大家,其实分布式协调技术主要用来解决分布式环境当中多个进程之间的同 ...
- java使用Redis5--分布式存储
Redis实现分布式存储的方法主要是采用一致性哈稀分片(Shard),将不同的key分配到不同的redis server上,达到横向扩展的目的. package redis; import java. ...
- 由一个场景分析Mysql的join原理
背景 这几天同事写报表,sql语句如下 select * from `sail_marketing`.`mk_coupon_log` a left join `cp0`.`coupon` c on c ...
- Linux命令行测试网速speedtest.net
Linux命令行测试网速speedtest.net 当发现上网速度变慢时,人们通常会先首先测试自己的电脑到网络服务提供商(通常被称为"最后一公里")的网络连接速度.在可用于测试宽带 ...
- Android实用代码片段
有时候,需要一些小的功能,找到以后,就把它贴到了博客下面,作为留言,查找起来很不方便,所以就整理一下,方便自己和他人. 一. 获取系统版本号: 1 PackageInfo info = this.g ...
- Myeclipse修改项目名称发布后web Context root名称无法修改
选中项目,右键--->Properties--->在搜索框搜索:deployment as 然后点击显示出的搜索项.修改右侧视图的Web Context Root名称即可. 如图: