从 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 是引用类型的有效值,因此不需要转换。

如果您想保留 SomeNone,则不能将 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) { ... }

这样做的好处是更具可读性和明确性。