当前位置: 首页 > news >正文

只有你能 new 出来!.NET 隐藏构造函数的 n 种方法(Builder Pattern / 构造器模式)

如果你给类写了一个公有构造函数,那么这个类就能被其他开发者 new 出来。如果你不想让他们 new 出来,把构造函数 private 就好了呀。

然而还有更多奇怪的方式来隐藏你类的构造方法。


为什么要隐藏构造函数?

有些类型,只有组件的设计者才知道如何正确创建其类型的实例,多数开发者都无法正确将其创建出来。典型的如 string:绝大多数开发者都不能正确创建出 string 的实例,但通过写一个字符串由编译器去创建,或者使用 StringBuilder 来构造则不容易出错。

再或者,我们只希望开发者使用到某个抽象的实例,而不是具体的类型,那么这个时候开发者也需要有方法能够拿到抽象接口的实例。我们可能会使用工厂或者某些其他的方法让开发者在不知道具体类型的时候获取到抽象类型的实例。

这正是构造器模式的典型应用场景。在维基百科中对它适用性的描述为:

在以下情况使用生成器模式:

  • 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时;
  • 当构造过程必须允许被构造的对象有不同的表示时。

详见:生成器模式 - 维基百科,自由的百科全书

接下来,我们使用一些奇怪的方式来创建对象的实例,完完全全把构造函数隐藏起来。

隐式转换和显式转换

典型的像 long a = 1;bool? b = true 这都是语法级别的隐式转换。这真的只是语法级别的隐式转换,实际上这两个都是编译器原生支持,编译时即已转换为真实的类型了。

[System.Runtime.Versioning.NonVersionable]
public static implicit operator Nullable<T>(T value)
{
    return new Nullable<T>(value);
}

[System.Runtime.Versioning.NonVersionable]
public static explicit operator T(Nullable<T> value)
{
    return value.Value;
}

于是我们可以考虑写一个神奇的类,其创建是通过隐式转换来实现的:

Fantastic fantastic = "walterlv";
Console.WriteLine(fantastic);

以上代码的输出是 walterlv is fantastic

namespace Walterlv.Demo.Patterns
{
    public class Fantastic
    {
        private readonly string _value;
        private Fantastic(string value) => _value = value;
        public static implicit operator Fantastic(string value) => new Fantastic(value);
        public override string ToString() => $"{_value ?? "null"} is fantastic.";
    }
}

而使用显式转换,我们还可以写出更奇怪的代码来。比如下面这个,我们的实例是通过强制转换一个 null 来实现的:

Fantastic fantastic = (IFantastic) null;
Console.WriteLine(fantastic);

以上代码的输出是 is fantastic 字符串。呃……前面有个空格。

namespace Walterlv.Demo.Patterns
{
    public class Fantastic
    {
        private readonly IFantastic _value;
        private Fantastic(IFantastic value) => _value = value;
        public static implicit operator Fantastic(IFantastic value) => new Fantastic(value);
        public override string ToString() => $"{_value} is fantastic.";
    }

    public class IFantastic
    {
    }
}

那个 IFantastic 必须得是一个类,而不能是接口,因为隐式转换不能从接口转,也不能转到接口。

在这里插入图片描述
▲ 不能定义从接口进行的隐式转换

运算符重载

使用运算符重载,也可以让类型实例的构造隐藏起来。比如下面的 Scope 类型,从字符串创建,然后通过与不同的字符串进行位或运算来得到其他的 Scope 的实例。

Scope scope = "A";
var full = scope | "B" | "C";
Console.WriteLine(full);

当然这段代码也少不了隐式转换的作用。

以上 Scope 类型的实现在 github 上开源,其表示 OAuth 2.0 中的 Scope

ERMail/Scope.cs

关于运算符重载的更多内容,可以参考我的另外两篇文章:

  • C# 中那些可以被重载的操作符,以及使用它们的那些丧心病狂的语法糖 - walterlv
  • C# 空合并操作符(??)不可重载?其实有黑科技可以间接重载! - walterlv

我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

相关文章:

  • UWP 中的各种文件路径(用户、缓存、漫游、安装……)
  • 使用并解析 OPML 格式的订阅列表来转移自己的 RSS 订阅(解析篇)
  • 使用并解析 OPML 格式的订阅列表来转移自己的 RSS 订阅(概念篇)
  • csproj 文件中那个空的 NuGetPackageImportStamp 是干什么的?
  • C#/.NET 中 Thread.Sleep(0), Task.Delay(0), Thread.Yield(), Task.Yield() 不同的执行效果和用法建议
  • WPF 中那些可跨线程访问的 DispatcherObject(WPF Free Threaded Dispatcher Object)
  • 在 Visual Studio Code 中为代码片段(Code Snippets)添加快捷键
  • 在 Visual Studio 中使用 EditorConfig 统一代码风格(含原生与插件)
  • 在 Visual Studio Code 中添加自定义的代码片段
  • 用 dotTrace 进行性能分析时,Timeline 打不开?无法启动进程?也许你需要先开启系统性能计数器的访问权限
  • 了解 .NET/C# 程序集的加载时机,以便优化程序启动性能
  • git 如何更可靠地解决冲突?
  • .NET 编写一个可以异步等待循环中任何一个部分的 Awaiter
  • 文件被占用?系统自带的“资源监视器(resmon)”也能帮你找到占用它的真凶
  • Windows 系统文件资源管理器的命令行参数(如何降权打开程序,如何选择文件)
  • 【划重点】MySQL技术内幕:InnoDB存储引擎
  • es6(二):字符串的扩展
  • HashMap剖析之内部结构
  • input实现文字超出省略号功能
  • java架构面试锦集:开源框架+并发+数据结构+大企必备面试题
  • JS学习笔记——闭包
  • niucms就是以城市为分割单位,在上面 小区/乡村/同城论坛+58+团购
  • 阿里云购买磁盘后挂载
  • 动手做个聊天室,前端工程师百无聊赖的人生
  • 官方新出的 Kotlin 扩展库 KTX,到底帮你干了什么?
  • 基于HAProxy的高性能缓存服务器nuster
  • 将 Measurements 和 Units 应用到物理学
  • 利用DataURL技术在网页上显示图片
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 携程小程序初体验
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 一、python与pycharm的安装
  • k8s使用glusterfs实现动态持久化存储
  • mysql面试题分组并合并列
  • #免费 苹果M系芯片Macbook电脑MacOS使用Bash脚本写入(读写)NTFS硬盘教程
  • (1综述)从零开始的嵌入式图像图像处理(PI+QT+OpenCV)实战演练
  • (pojstep1.1.1)poj 1298(直叙式模拟)
  • (二)【Jmeter】专栏实战项目靶场drupal部署
  • (未解决)macOS matplotlib 中文是方框
  • (原创)攻击方式学习之(4) - 拒绝服务(DOS/DDOS/DRDOS)
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • (转)shell调试方法
  • .apk文件,IIS不支持下载解决
  • .class文件转换.java_从一个class文件深入理解Java字节码结构
  • .NET Core 2.1路线图
  • .NET:自动将请求参数绑定到ASPX、ASHX和MVC(菜鸟必看)
  • .NET性能优化(文摘)
  • .NET中使用Protobuffer 实现序列化和反序列化
  • ?
  • [《百万宝贝》观后]To be or not to be?
  • [ACL2022] Text Smoothing: 一种在文本分类任务上的数据增强方法
  • [ai笔记4] 将AI工具场景化,应用于生活和工作
  • [android] 看博客学习hashCode()和equals()
  • [Android]如何调试Native memory crash issue
  • [BZOJ2208][Jsoi2010]连通数