从 null 隐式转换
Implicit conversion from null
查看 PluralSight 上的 Zoran Horvats 课程,我目前正在实施 Maybe 类型,有点像 Zoran 在其 GitHub 帐户上的课程:https://github.com/zoran-horvat/option
通常,Maybe 是对象的包装器,这些对象要么设置为空值,要么具有空值,从而避免空引用异常。
为了使代码更短一些,我想使用隐式转换将值/空值映射到它们对应的可能类型。这是我的代码示例:
public void Hook(Maybe<Action<Keys>> onKeyDown, Maybe<Action<Keys>> onKeyUp)
{
_keyDownCallback = onKeyDown;
_keyUpCallback = onKeyUp;
_hookService.Hook(HookType.KeyBoardLowLevel, OnHookReceived);
}
如您所见,您可以挂接并传递两个可选的回调,一个用于 keyDown,一个用于 keyUp。我想传递这样的代码:
nativeKeyboardHookService.Hook(new Action<Keys>(OnNativeKeyDown), null);
Maybe 上的隐式转换目前是这样实现的:
public static implicit operator Maybe<T>(T value)
{
return ToMaybe(value);
}
public static implicit operator T(Maybe<T> maybe)
{
return ToT(maybe);
}
public static Maybe<T> ToMaybe(T value)
{
if (value == null)
{
return new None<T>();
}
return new Some<T>(value);
}
public static T ToT(Maybe<T> maybe)
{
return maybe.Evaluate(
value => value,
() => default(T));
}
我的问题:它工作正常,如果我传递一个实际对象,将它映射到一个 Maybe,但如果我传递 NULL,我仍然得到一个 NULL 对象,而不是 None 对象。我在这里做错了什么还是不可能的?我没有找到关于此类转换的任何进一步信息。
您的 Maybe<T>
仍然是引用类型,因此 null
是它的有效值:
Maybe<string> foo = null;
如果你想防止这种情况发生,你需要将其设为值类型,例如:
public struct Maybe<T>
{
public T Value { get; }
public bool IsEmpty => Value == null;
public Maybe(T value)
{
Value = value;
}
public static implicit operator Maybe<T>(T value)
{
return new Maybe<T>(value);
}
}
然后您可以将 null
传递给需要 Maybe<T>
的方法,它将正确构造一个空的 Maybe<T>
对象。
但请注意,它是一个值类型,这意味着它在每次方法调用时都会被复制,因此它与引用类型实现具有不同的行为。
最后,您无法在 C# 中很好地实现这一点,因为语言中 是 空引用。只有使用 C# 8 的可空引用类型,您才能完全避免空值。
当您将 null
传递给 Hook()
时,这实际上就是您所做的一切,因为您的隐式转换根本没有被调用。那是因为 null
是引用类型的有效值,因此不需要转换。
如果您想保留 Some
和 None
,则不能将 Maybe
更改为结构,因为那样它们也必须是结构,这意味着您 运行进入你不能继承结构的问题。
您也无法实现通用 IMaybe<T>
接口,因为接口不能与转换一起使用。
我的建议是保持你的行为不变,但不要使用 null
。不要传递 null
,而是传递其他内容,例如 Maybe<T>.None
:
class Maybe<T>
{
public static Maybe<T> None { get; } = new None<T>();
}
void Hook(..., Maybe<T>.None) { ... }
或None<T>.Instance
:
class None<T>
{
public static None<T> Instance{ get; } = new None<T>();
}
void Hook(..., None<T>.Instance) { ... }
这样做的好处是更具可读性和明确性。
查看 PluralSight 上的 Zoran Horvats 课程,我目前正在实施 Maybe 类型,有点像 Zoran 在其 GitHub 帐户上的课程:https://github.com/zoran-horvat/option
通常,Maybe 是对象的包装器,这些对象要么设置为空值,要么具有空值,从而避免空引用异常。 为了使代码更短一些,我想使用隐式转换将值/空值映射到它们对应的可能类型。这是我的代码示例:
public void Hook(Maybe<Action<Keys>> onKeyDown, Maybe<Action<Keys>> onKeyUp)
{
_keyDownCallback = onKeyDown;
_keyUpCallback = onKeyUp;
_hookService.Hook(HookType.KeyBoardLowLevel, OnHookReceived);
}
如您所见,您可以挂接并传递两个可选的回调,一个用于 keyDown,一个用于 keyUp。我想传递这样的代码:
nativeKeyboardHookService.Hook(new Action<Keys>(OnNativeKeyDown), null);
Maybe 上的隐式转换目前是这样实现的:
public static implicit operator Maybe<T>(T value)
{
return ToMaybe(value);
}
public static implicit operator T(Maybe<T> maybe)
{
return ToT(maybe);
}
public static Maybe<T> ToMaybe(T value)
{
if (value == null)
{
return new None<T>();
}
return new Some<T>(value);
}
public static T ToT(Maybe<T> maybe)
{
return maybe.Evaluate(
value => value,
() => default(T));
}
我的问题:它工作正常,如果我传递一个实际对象,将它映射到一个 Maybe,但如果我传递 NULL,我仍然得到一个 NULL 对象,而不是 None 对象。我在这里做错了什么还是不可能的?我没有找到关于此类转换的任何进一步信息。
您的 Maybe<T>
仍然是引用类型,因此 null
是它的有效值:
Maybe<string> foo = null;
如果你想防止这种情况发生,你需要将其设为值类型,例如:
public struct Maybe<T>
{
public T Value { get; }
public bool IsEmpty => Value == null;
public Maybe(T value)
{
Value = value;
}
public static implicit operator Maybe<T>(T value)
{
return new Maybe<T>(value);
}
}
然后您可以将 null
传递给需要 Maybe<T>
的方法,它将正确构造一个空的 Maybe<T>
对象。
但请注意,它是一个值类型,这意味着它在每次方法调用时都会被复制,因此它与引用类型实现具有不同的行为。
最后,您无法在 C# 中很好地实现这一点,因为语言中 是 空引用。只有使用 C# 8 的可空引用类型,您才能完全避免空值。
当您将 null
传递给 Hook()
时,这实际上就是您所做的一切,因为您的隐式转换根本没有被调用。那是因为 null
是引用类型的有效值,因此不需要转换。
如果您想保留 Some
和 None
,则不能将 Maybe
更改为结构,因为那样它们也必须是结构,这意味着您 运行进入你不能继承结构的问题。
您也无法实现通用 IMaybe<T>
接口,因为接口不能与转换一起使用。
我的建议是保持你的行为不变,但不要使用 null
。不要传递 null
,而是传递其他内容,例如 Maybe<T>.None
:
class Maybe<T>
{
public static Maybe<T> None { get; } = new None<T>();
}
void Hook(..., Maybe<T>.None) { ... }
或None<T>.Instance
:
class None<T>
{
public static None<T> Instance{ get; } = new None<T>();
}
void Hook(..., None<T>.Instance) { ... }
这样做的好处是更具可读性和明确性。