C#的泛型的类型参数可以有带参数的构造函数的约束方式吗?
Review后看到标题让我十分羞愧自己语文功底太差,估计...请见谅......我还特地把这句写回开头了......
问题
前天遇到的一个问题,所以在MSDN发了个问,刚也丰富了下问题,关于泛型的。
最近用EF尝试DDD常常有些奇怪的想法,比如“EF的Model First能否添加泛型支持”。这次是“泛型的类型能否有带参数的约束方式”。
具体想法很简单,在我使用泛型的时候,我发现我需要实例化一个类型参数:
class MyClass<T>
{
public MyClass1()
{
this.MyObject = new T();
} T MyObject { get; set; }
}
当然,上面会报错。
错误内容是T没有一个new约束(new constraint),查阅下MSDN,得到了泛型的类型参数的new约束的内容。
所以接下来正确的代码就是:
class MyClass<T>
where T : new()
{
public MyClass1()
{
this.MyObject = new T();
} T MyObject { get; set; }
}
然后,后来我发现,我需要根据参数来创建新的对象,而且该方法在泛型的构造函数中实现最合适,所以我希望有这样的代码:
class MyClass1<T>
where T : new(string)
{
public MyClass(string request)
{
this.MyObject = new T(request);
} T MyObject { get; set; }
}
可惜这下就错大了,然后查阅泛型的约束方式列表,发现根本没有带参数的构造函数这种约束。
所以就发生了我上面在MSDN上问的那个问题,寻求一个“优雅的解决方案”。
一般解决方案就像问题中的回答那样有两种,也是我试过但是很不爽的两种,我们依次看看。
补充:
- 由James.Ying提醒,还有从构造函数传入
- 由@Choo提醒,用Activator.CreateInstance
工厂模式
首先是Factory Pattern,就是建一个工厂类,先看看代码,这是其中一种写法,请不要纠结在Factory Pattern上:
class MyClass<T, TFactory>
where TFactory : IFactory<T>, new()
{
public MyClass(string request)
{
var factory = new TFactory(); this.MyObject = factory.New(request);
} T MyObject { get; set; }
} interface IFactory<T>
{
T New(string request);
}
实现中你会发现,这样需要为每个派生类或者实例类别创建并维护一个Factory类,那样泛型本身就没那么大意义了,本来就是为了减少类型重用逻辑而采用泛型的。
抽象基类的静态抽象方法
如果不想维护多一个类,那么就在目标类本身下手,所以我们可以为目标类创建一个基类:
class MyClass<T>
where T : TBase, new()
{
public MyClass(string request)
{
this.MyObject = T.New(request);
} T MyObject { get; set; }
} abstract class TBase
{
public abstract static TBase New(string request);
}
为了防止误人子弟,首先要说在前头的是,这样写是会编译错误的!
约束上是没错的,但是它报的错误是类似于“T是个类型参数,不能这么用!”('T' is a 'type parameter', which is not valid in the given context)。
从构造函数传入
还有一种基础的做法反而忘记了,由James.Ying提醒想起来,就是从泛型类的构造函数传入。
class MyClass<T>
where T : TBase, new()
{
public MyClass(T myObject)
{
this.MyObject = myObject;
} T MyObject { get; set; }
}
这种方式使得泛型类简洁多了,把实例化的过程交给了调用者,有点依赖倒置了(其实凡是应该在泛型里实现的而交给了调用者或者继承者都是这样)。
优点是泛型简单了,缺点就是你无法保证实例化使用的构造函数是T(string)。另外,它可能会降低代码的重用性。假设实例化是有条件地,而且所有派生类的逻辑是统一的,那么还是在泛型基类中实现比较好。
简单情况下这是对泛型来说最优雅的方式了。
Activator.CreateInstance
该方法可以在http://msdn.microsoft.com/en-us/library/system.activator.createinstance(v=vs.110).aspx见到,说明就比较明确了:
用最匹配的构造函数创建一个类型的实例(Creates an instance of the specified type using the constructor that best matches the specified parameters)。
写法也很爽:
class MyClass<T>
{
public MyClass(string request)
{
this.MyObject = (T)Activator.CreateInstance(typeof(T), request);
} T MyObject { get; set; }
}
这种方法做得到,也很简短,也不用多做接口和基类。
缺点就是没有约束,没办法保证T能有带指定数量和类型参数的构造函数,或者是否有构造函数。
如果T不符合设计需求的话会报相应的异常。
原来泛型的类型参数是这么设计的
至此,便可以知道,C#的泛型里,类型参数是一种“非类”的存在,类型参数的约束(Constraints on Type Parameters)仅仅是用来描述具体的类在实例化或者继承时所需要达到的条件。而在泛型内部,类型参数仅仅是一种“特别的存在”,它用来描述类,但却无法用作类。
那么,类型参数可以有......参考本文题目......
首先,其实这个问题本身就是泛型的类型参数能否有带参数的实例化方式,比如 T myObject = new T("Hello World!“) 。
然后,由于类型参数是用“约束”的方式来进行实例类的特点的描述的,所以,问题才变成了泛型的类型参数能否有带参数的构造函数的约束方式,比如 where T : new(string) 。
要做假设的话,起始就是个证伪的问题,要证明它存在是否会造成什么原则问题。
首先能对比的就是泛型的类型参数已经有了不带参数的构造函数的约束方式了,那么泛型的类型参数就算有带了参数的构造函数的约束方式又如何?至少,泛型的类型参数已经有了不带参数的构造函数的约束方式证明了泛型的类型参数有构造函数的约束方式并不会造成什么问题而且技术上是可以实现的。(......)
在我们实例化一个新对象的时候通常会用两种初始化方式:
- 利用构造函数传参
- 实例化后赋值
大部分情况下两种方式产生的结果是差不多的,这种大部分情况是指一般所涉及到的属性或参数都是公开的(public),本来就是开放读写的,所以内部写和外部写都差不多。
但遇到一些情况,比如一些业务约束,需要对参数进行处理或者利用参数进行操作,最终操作结果是私密的(private),那么就会偏向于选用构造函数传参。或者会使用一个特殊的方法,由该方法在类实例化之后再把需要的数据带进来进行操作,这么做些许有失“一气呵成”的爽快。
利用构造函数传参并不是什么容易替代的方式,因为它在绝大部分属于它的场景里都是最优的解决方案。有时候,初始化一个对象到使用,一气呵成是最好的,因为这个事务本身就有很强的原子性。一个对象的两种初始化方式造成了双入口的麻烦,作为该类的使用者,有时候你会模糊,两种方式所产生的结果你无法准确地把握;对于开发者,两种实现方式供的出现在规范上也要求要么二选一,要么保证两者一致。当类变得相对复杂的时候,事情就没那么简单了。
所以,我们确实会需要泛型的类型参数有带了参数的构造函数的约束方式的一些场景。它虽然不是必要的,但是绝对是一种需要,就像get/set访问器那样。
补充地说,其实更大的命题是类型参数是否可以当作类使用
假设它可以由带参数的构造函数约束了,那么可不可以直接如约束那样当作类来使用呢?比如调用静态方法?在泛型中创建继承于该类型参数的类?
如此种种算来发现每一种都有可能是特例,而不是一个简单的实现即可解决的。
比如调用静态方法来说,T.Hello()所涉及的就是执行的时候T能明确是哪个类;而在泛型类中创建继承于该类型参数的类就会变得复杂。
单单想想调用那个类的方法:MyClass<T>.MySubClass,这里的语法就有点“不一般”了,未必是一个“仅仅是泛型本身的问题”。
逼格高一点地说,越来越多的功能对C#或者任何一门语言来说是一条正确的道路吗?
关于题目
如果你有不满,可以提供合适的标题,禁止以任何方式攻击作者!
C#的泛型的类型参数可以有带参数的构造函数的约束方式吗?的更多相关文章
- C# 利用反射动态创建对象——带参数的构造函数和String类型
C# 利用反射动态创建对象——带参数的构造函数和String类型 最近笔者有一个想法需要利用反射动态创建对象(如string,int,float,bool,以及自定义类等)来实现,一直感觉反射用不好, ...
- mfc 带参数的构造函数
知识点 默认的构造函数 带参数的构造函数 重载构造函数 一.默认的构造函数 二.带参数的构造函数 三.重载构造函数 class Tdate { public: int year;//年 int mon ...
- C#利用反射动态创建对象 带参数的构造函数和String类型 (转载)
最近笔者有一个想法需要利用反射动态创建对象(如string,int,float,bool,以及自定义类等)来实现,一直感觉反射用不好,特别是当构造函数带参数的时候.MSDN上给出的例子十分复杂,网上的 ...
- JavaScript--关于实例对象带不带参数和构造函数带不带参数的关系
就是一句话: 构造函数创建对象时,也可以带参数,因为可以对对象进行一些属性的初始化,也就是你创建对象时,就带着这些属性,当然你也可以不带参数,后面实例化对象后再进行添加.而且,js函数的参数在定义函数 ...
- [转载] C++ 类中的类成员变量怎么调用带参数的构造函数来初始化?
#include "stdafx.h" class A { public: A(){ax = ;}; A(int a){ax = a;}; int ax; }; class B { ...
- mock带参数的构造函数
@RunWith(PowerMockRunner.class)@PrepareForTest(Helper.class)//1.添加要初始化的类,就是构造函数所在的类public class Help ...
- c++中在一个类中定义另一个只有带参数构造函数的类的对象
c++中在一个类中定义另一个只有带参数构造函数的类的对象,编译通不过 #include<iostream> using namespace std; class A { public: ...
- C#泛型对类型参数的推断
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- c# 泛型<T>类型参数T的约束where
在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制.如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误.这些限制称为约束.约束是使用 where 上 ...
随机推荐
- 纯CSS3实现的一些酷炫效果
之前在网上看到一些用纯CSS3实现的酷炫效果,以为实现起来比较困难,于是想看看具体是怎么实现的. 一.笑脸猫动画 实现效果如下: 这个实现起来确实比较麻烦,很多地方需要花时间,有耐心地调整. 1.先看 ...
- 使用Monit监控本地进程
目前用它监控某些服务,失败自动重启,同时监控特定的日志文件,如果有变化,就发邮件报警 安装不细写了,网上好多 我先用cat /proc/version看了下我的系统是el6的,于是wget http: ...
- 百度 flash html5自切换 多文件异步上传控件webuploader基本用法
双核浏览器下在chrome内核中使用uploadify总有302问题,也不知道如何修复,之所以喜欢360浏览器是因为帮客户控制渲染内核: 若页面需默认用极速核,增加标签:<meta name=& ...
- SignalR SelfHost实时消息,集成到web中,实现服务器消息推送
先前用过两次SignalR,但是中途有段时间没弄了,今天重新弄,发现已经忘得差不多了,做个笔记! 首先创建一个控制台项目Nuget添加引用联机搜索:Microsoft.AspNet.SignalR.S ...
- [C#] 走进异步编程的世界 - 开始接触 async/await
走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async/await,但在控制台输出示例时经常会采用 C# 6.0 的 $&qu ...
- My TWI
前言 对TWI没有什么特别的印象,因为有一个更出名的TWU,而我去年又刚好错过了它,因此TWU的光辉完全掩盖了TWI.对TWI印象最深的是在邮件中看到的38th北京的这期,看到他们的图文记录,在圈子中 ...
- OEL上使用yum install oracle-validated 简化主机配置工作
环境:OEL 5.7 + Oracle 10.2.0.5 RAC 如果你正在用OEL(Oracle Enterprise Linux)系统部署Oracle,那么可以使用yum安装oracle-vali ...
- Javascript正则对象方法与字符串正则方法总结
正则对象 var reg = new Regexp('abc','gi') var reg = /abc/ig 正则方法 test方法(测试某个字符串是否匹配) var str = 'abc123'; ...
- ActiveRecord模式整理
DAO Data Access Object,数据访问对象 DAO是一个软件设计的指导原则,在核心J2EE模式中是这样介绍DAO模式的:为了建立一个健壮的J2EE应用,应该将所有对数据源的访问操作抽象 ...
- 【从零开始学BPM,Day5】报表配置及自定义功能页面开发
[课程主题] 主题:5天,一起从零开始学习BPM [课程形式] 1.为期5天的短任务学习 2.每天观看一个视频,视频学习时间自由安排. [第五天课程] 1.课程概要 Step 1 软件下载:H3 BP ...