我正在尝试实现一个系统,我需要一个 C# 类,其属性类型可以是
one of
类型。
在 F# 中,此功能称为“可区分联合”,在 TypeScript 中称为“联合类型”。
这是我如何在 TypeScript 中实现它的示例:
class Something {
public myField?: boolean | Date;
}
let something = new Something();
something.myField = true;
something.myField = new Date();
据我所知,这在 C# 中是不可能的。有哪些解决方法?
您(作为 C# 开发人员)将如何实现这个?
使用
object
类型可能适合我的情况,但我想要更精确的解决方案。
我也尝试过使用隐式转换,但它看起来太hacky
class MyFieldType {
private bool _valueBool;
private DateTime _valueDateTime;
public MyFieldType(DateTime value) {
_valueDateTime = value;
}
public MyFieldType(bool value) {
_valueBool = value;
}
public bool GetBool() {
return _valueBool;
}
public DateTime GetDateTime() {
return _valueDateTime;
}
public static implicit operator MyFieldType(bool value) {
return new MyFieldType(value);
}
public static implicit operator MyFieldType(DateTime value) {
return new MyFieldType(value);
}
public static implicit operator DateTime(MyFieldType value) {
return value.GetDateTime();
}
public static implicit operator bool(MyFieldType value) {
return value.GetBool();
}
}
class MyClass {
public MyFieldType? MyField;
}
除了我多年来见过的所有hacky选项之外,我还知道两种好的、忠实的表示:Church编码和Visitor设计模式。
对 OP 示例进行 Church 编码的一种方法是:
public sealed class ChurchSomething
{
private readonly IChurchSomething imp;
private ChurchSomething(IChurchSomething imp)
{
this.imp = imp;
}
public static ChurchSomething CreateBool(bool b)
{
return new ChurchSomething(new Boolean(b));
}
public static ChurchSomething CreateDate(DateTime date)
{
return new ChurchSomething(new Date(date));
}
public T Match<T>(Func<bool, T> onBoolean, Func<DateTime, T> onDate)
{
return this.imp.Match(onBoolean, onDate);
}
private interface IChurchSomething
{
T Match<T>(Func<bool, T> onBoolean, Func<DateTime, T> onDate);
}
private sealed class Boolean(bool b) : IChurchSomething
{
public T Match<T>(Func<bool, T> onBoolean, Func<DateTime, T> onDate)
{
return onBoolean(b);
}
}
private sealed class Date(DateTime dt) : IChurchSomething
{
public T Match<T>(Func<bool, T> onBoolean, Func<DateTime, T> onDate)
{
return onDate(dt);
}
}
}
虽然 Church 编码是表示和类型的最简单方法,但许多人发现它看起来并不面向对象。如果是这样,您可以将其重构为访客。
OP 示例的基于访问者的表示可能如下所示:
public interface ISomethingVisitor<T>
{
T VisitBool(bool b);
T VisitDate(DateTime dt);
}
public sealed class VisitorSomething
{
private readonly IVisitorSomething imp;
private VisitorSomething(IVisitorSomething imp)
{
this.imp = imp;
}
public static VisitorSomething CreateBool(bool b)
{
return new VisitorSomething(new Boolean(b));
}
public static VisitorSomething CreateDate(DateTime date)
{
return new VisitorSomething(new Date(date));
}
public T Accept<T>(ISomethingVisitor<T> visitor)
{
return this.imp.Accept(visitor);
}
private interface IVisitorSomething
{
T Accept<T>(ISomethingVisitor<T> visitor);
}
private sealed class Boolean(bool b) : IVisitorSomething
{
public T Accept<T>(ISomethingVisitor<T> visitor)
{
return visitor.VisitBool(b);
}
}
private sealed class Date(DateTime dt) : IVisitorSomething
{ public T Accept<T>(ISomethingVisitor<T> visitor)
{
return visitor.VisitDate(dt);
}
}
}
在这两个示例中,我将一些接口定义为嵌套到实现本身。我经常这样做,因为在这些情况下,接口的目的只是为了使实现利用多态性。制作这些接口
private
也是可能的,但我有时担心天真的客户端开发人员会理解这是他们必须实现这些接口的信号意图 - 事实并非如此。