如何正确地避免对 `default(T)` 的原始痴迷?
How to avoid primitive obsession correctly with handling of `default(T)`?
我想避免对追随 struct
的原始痴迷。我这样做有两个目标:
- 使方法签名更诚实
- 确保不存在无效值
实施:
public struct SomeId : IEquatable<SomeId>
{
public static readonly SomeId Empty = new SomeId(String.Empty);
private SomeId(string someId)
{
Value = someId;
}
public string Value { get; }
public static bool operator ==(SomeId left, SomeId right) => left.Equals(right);
public static bool operator !=(SomeId left, SomeId right) => !(left == right);
public static implicit operator string(SomeId id) => id.Value;
public override int GetHashCode() => Value.GetHashCode();
public override bool Equals(object obj) => Value.Equals(obj);
public bool Equals(SomeId other) => String.Equals(Value, other.Value, StringComparison.Ordinal);
public static SomeId? Create(string value)
{
if (String.IsNullOrWhiteSpace(value))
{
return new SomeId(value);
}
return null;
}
}
代码很多,但还不够完美!事实上,它解决了主要问题,我不会到处传递字符串,而是为方法提供有意义的签名。
不过,我可以通过 default(SomeId)
创建新实例将无效值偷偷带入我的应用程序 - 因为 Value
是 string
类型,我会得到 SomeId
处于无效状态 Value = null
.
最好的解决方案是什么?我应该关心吗?
当然可以,例如:
private string _value;
public string Value => _value ?? String.Empty
...但是每当我访问这个 属性 时额外的空检查让我很烦。
你应该关心吗?是的,我想是这样。创建零初始化结构(default(SomeId)
、new SomeId()
、new SomeId[n]
)非常容易,并且您的结构在该状态下语义无效。
您有几个选择:
- getter 中的空合并(您提出的解决方案)。你是对的,如果该字段为空,那总是会导致执行更多的指令。问题是,那些额外的指令(例如加载空值、比较相等性、加载静态字段)是否对执行速度有可衡量的影响?
- 检查 getter 中的
null
并根据需要将字段设置为 string.Empty
。从技术上讲,它是一个 getter 有副作用(即使数据被封装),有些人对此有强烈的意见,但你也可以称之为惰性初始化。
- 声明默认实例无效,就像
ImmutableArray<T>
那样。
我想避免对追随 struct
的原始痴迷。我这样做有两个目标:
- 使方法签名更诚实
- 确保不存在无效值
实施:
public struct SomeId : IEquatable<SomeId>
{
public static readonly SomeId Empty = new SomeId(String.Empty);
private SomeId(string someId)
{
Value = someId;
}
public string Value { get; }
public static bool operator ==(SomeId left, SomeId right) => left.Equals(right);
public static bool operator !=(SomeId left, SomeId right) => !(left == right);
public static implicit operator string(SomeId id) => id.Value;
public override int GetHashCode() => Value.GetHashCode();
public override bool Equals(object obj) => Value.Equals(obj);
public bool Equals(SomeId other) => String.Equals(Value, other.Value, StringComparison.Ordinal);
public static SomeId? Create(string value)
{
if (String.IsNullOrWhiteSpace(value))
{
return new SomeId(value);
}
return null;
}
}
代码很多,但还不够完美!事实上,它解决了主要问题,我不会到处传递字符串,而是为方法提供有意义的签名。
不过,我可以通过 default(SomeId)
创建新实例将无效值偷偷带入我的应用程序 - 因为 Value
是 string
类型,我会得到 SomeId
处于无效状态 Value = null
.
最好的解决方案是什么?我应该关心吗?
当然可以,例如:
private string _value;
public string Value => _value ?? String.Empty
...但是每当我访问这个 属性 时额外的空检查让我很烦。
你应该关心吗?是的,我想是这样。创建零初始化结构(default(SomeId)
、new SomeId()
、new SomeId[n]
)非常容易,并且您的结构在该状态下语义无效。
您有几个选择:
- getter 中的空合并(您提出的解决方案)。你是对的,如果该字段为空,那总是会导致执行更多的指令。问题是,那些额外的指令(例如加载空值、比较相等性、加载静态字段)是否对执行速度有可衡量的影响?
- 检查 getter 中的
null
并根据需要将字段设置为string.Empty
。从技术上讲,它是一个 getter 有副作用(即使数据被封装),有些人对此有强烈的意见,但你也可以称之为惰性初始化。 - 声明默认实例无效,就像
ImmutableArray<T>
那样。