如何为 null 类型实现泛型接口?

How to implement generic interface for null type?

我有以下代码:

public interface IResult
{
    StatusCode Code { get; }
    string Error { get; }
}

public interface IResult<T> : IResult
{
    T Value { get; }
}

public class Result : IResult
{
    public StatusCode Code { get; set; }
    public string Error { get; set; }
}

public class Result<T> : IResult<T>
{
    public StatusCode Code { get; set; }
    public string Error { get; set; }
    public T Value { get; set; }
}

我有两个接口:IResult 用于没有值的结果(如 void),IResult<T> 用于需要值的结果。但我不想支持两种不同的类型。我想将它们合并为单一类型。如果结果有值,该值就会存在,而对于我没有任何值的情况,它将只是 null.

所以它是这样的: (涵盖所有情况)

public interface IResult<T>
{
    StatusCode Code { get; }
    string Error { get; }
    T Value { get; }
}

public class Result<T> : IResult<T>
{
    public StatusCode Code { get; set; }
    public string Error { get; set; }
    public T Value { get; set; }
}

但我正在努力处理 Tempty/null 的情况。我如何为这种情况定义 class ? 到目前为止我只找到一个想法:

public class Result<NullValue> : IResult<NullValue>
{
    public StatusCode Code { get; set; }
    public string Error { get; set; }
    public NullValue Value { get; set; }
}

public class NullValue
{
}

我正在寻找除此之外的任何其他想法:

public class ResultWithNoValue : IResult<T>
{
    public StatusCode Code { get; set; }
    public string Error { get; set; }
    public object Value => null;
}

它不起作用,因为我必须使用:

public interface IBaseResponse< out T>
{
    IResult<T> Data { get; }
}

并且此代码不可编译:

public class ResponseWithNoValue : IBaseResponse<object>
{
    public ResultWithNoValue Data { get; }
}

And this code is not compilable:

public class ResponseWithNoValue : IBaseResponse<object>
{
    public ResultWithNoValue Data { get; }
}

没有,也没有为 Data 使用任何其他实现 class;只能是 IResult<object>:

public class ResponseWithNoValue : IBaseResponse<object>
{
    public IResult<object> Data { get; }
}

该方法可能适合您的需要。

当您创建 Result<object> 的实例时,请不要设置 Value。接口无论如何都是只获取的,所以它将是不可变的 null:

public class ResponseWithNoValue : IBaseResponse<object>
{
    public IResult<object> Data { get; }
    public ResponseWithNoValue(StatusCode code, string Error)
    {
        Data = new Result<object> { Code = code, Error = error };
    }
}

var response = new ResponseWithNoValue(SomeCode, "SomeError");
var val = response.Data.Value; // null
response.Data.Value = new object(); // Compilation error

空值的问题在于值类型不能为空。
您可以通过使用 default 而不是 null 来解决这个问题(如果结果是失败的结果,则不初始化 Value 属性)。

话虽这么说,我建议让你的 class 不可变:

public class Result<T> : IResult<T>
{
    public Result(T value)
    {
        Value = value;
    }

    public Result(StatusCode code, string error)
    {
        Code = code;
        Error = error;
    }

    public StatusCode Code { get; }
    public string Error { get; }
    public T Value { get; }
}

更好 - 使用私有构造函数和静态 Success/Fail 方法来创建 Result<T> class:

的实例
public interface IResult<T>
{
    StatusCode Code { get; }
    string Error { get; }
    T Value { get; }
}

public class Result<T> : IResult<T>
{

    public static Result<T> Success(T value)
    {
        return new Result<T>(null, "", value);
    }

    public static Result<T> Fail(StatusCode code, string Error)
    {
        return new Result<T>(code, error, default);
    }

    private Result(StatusCode code, string error, T value)
    {
        Code = code;
        Error = error;
        Value = value;
    }

    public StatusCode Code { get; }
    public string Error { get; }
    public T Value { get; }
}

用法:

var result = Result<int>.Success(5);

var result = Result<SomeType>.Fail(new StatusCode(1), "Failed to do something");

这使得通过阅读调用代码很容易理解结果是成功还是失败。

你说你不想有两种类型,但你没有解释为什么。这使得很难理解您实际要解决的问题。

由于问题目前的措辞,没有正当理由想要将这些类型合并为一个。它们不一样。一个有结果,一个没有。
它们之间有一些重叠,但这并不是删除其中一种类型的理由;这是让他们 share/reuse 一些共同逻辑的论据。

本质上,你的论点等同于说:

I don't like having a base class and a derived class, so I'm just going to make the derived class and not use some of the properties when I don't need them.

技术上可行吗?当然。这是个好主意吗?没有。它与静态类型、OOP 原则和一般的干净编码实践背道而驰。

通过合并这两种结果类型,您将忽略无值结果类型,从而导致您面临的确切问题:无法优雅地实现无值结果。


我怀疑您可能忽略了 Result<T> 可以继承自 Result 以及 实现 IResult<T> 接口:

// other interfaces/classes are unchanged

public class Result<T> : Result, IResult<T>
{
    public T Value { get; set; }
}

这允许您在处理 Result<T> 对象时 重用 一些 Result 逻辑,以防您不需要与潜在 return 价值。

请注意,这反映了类似的用例,例如异步代码中的 TaskTask<T> return 类型。类型不同是因为它们的处理方式不同。但是,任何 Task<T> 都可以像任何其他 Task 一样对待,因为 Task<T> : Task.


If a result has a value the value will be there and for cases where I do not have any value it will be just null.

那只是要求空引用异常。有针对 null 的有效案例,但我强烈建议不要创造更多不必要的原因来编写 null 检查。

通过依赖无值结果类型的静态类型,您可以有效地确保不需要空值检查。

由于给定方法 return 是无值结果还是有值结果这一事实无论如何都硬编码在方法主体中,因此方法的 return 类型实际上反映了这一点并没有任何损失还有。

这是让您的消费者清楚他们可以从您的方法中得到什么的问题。通过合并你的结果类型,你使它变得不清楚,因此不必要地混淆了你的界面。


另请注意,我不太认为需要在此处设置 IResult/IResult<T> 接口,因为您处理的本质上是值对象,但由于您的问题很简单在上下文中,可能有别有用心的原因让接口在这个问题中根本不明显。