【问题标题】:How to implement generic interface for null type?如何实现空类型的泛型接口?
【发布时间】:2020-11-12 00:03:29
【问题描述】:

我有以下代码:

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&lt;T&gt; 用于需要值的结果。但我不想支持两种不同的类型。我想将它们合并为单一类型。如果结果有值,则该值将存在,对于我没有任何值的情况,它将只是 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 的情况。如何为这种情况定义一个类? 到目前为止,我只发现了一个想法:

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; }
}

【问题讨论】:

  • 我不确定我是否理解...null 不是一个类型,它是一个“值”(好吧,没有值。如果该值应该是引用类型)。那么怎么会有“T 为空的情况”呢?如果您有一个无法确定的类型(我猜),那么您期望逻辑是什么?。
  • 为什么null 值需要单独的类型?为什么不使用 default 文字或保持 T Value 未设置,默认值(引用类型为 null
  • 适用于没有价值的情况。想象一下,有一个客户端向服务器发送命令以取消作业。服务器包含一个处理程序,它可以处理客户端请求并执行命令。取消本身是这样的:void Cancel(Guid id){//...} - 没有返回类型它是void。但是处理程序可能会失败或取消本身抛出异常。客户需要此信息。 IResult 描述它:错误和代码(如 OK、ClientError、ServerError)
  • @PavelAnikhouski 我没明白。类型会是什么样子?我需要在“Result”中添加T。我不知道在我不关心的情况下该放什么(不仅仅是没有价值)。
  • @isxaker - 那么为什么IResult&lt;object&gt; 不能在那里工作呢?或者有一个完全独立的、无类型的IResult { SatusCode { get; } string Error { get; } },然后有IResult&lt;T&gt; : IResult { T Value { get; } }

标签: c# .net generics inheritance interface


【解决方案1】:

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

正如问题目前所表述的那样,没有正当理由希望将这些类型合并为一个。她们不一样。一个有结果,另一个没有。
它们之间有一些重叠,但这不是删除其中一种类型的论据;这是让他们共享/重用一些通用逻辑的论据。

本质上,你的论点就是说:

我不喜欢有一个基类和一个派生类,所以我只做派生类,并且在我不需要的时候不使用一些属性。

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

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


我怀疑你可能忽略了Result&lt;T&gt; 可以从Result 继承以及实现IResult&lt;T&gt; 接口:

// other interfaces/classes are unchanged

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

这允许您在处理Result&lt;T&gt; 对象时重用一些Result 逻辑,以防您不需要与潜在的返回值进行交互。

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


如果结果有值,则该值将存在,而对于我没有任何值的情况,它将为空。

这只是要求空引用异常。 null 有一些有效的案例,但我强烈建议不要创造更多的理由来编写 null 检查而不是必要的。

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

由于给定方法返回无值结果或有值结果的事实无论如何都硬编码在方法主体中,因此方法的返回类型也实际反映这一点并没有什么损失。

这是一个让您的消费者清楚他们可以从您的方法中得到什么的问题。通过合并您的结果类型,您会使其不清楚,从而不必要地混淆您的界面。


另请注意,我不完全认为需要在此处使用 IResult/IResult&lt;T&gt; 接口,因为您正在处理本质上是值对象的内容,但由于您的问题涉及上下文,因此可能是不可告人的拥有这个问题中根本不明显的接口的原因。

【讨论】:

  • 我完全同意你的看法。我自己已经实现了一个 Result/Result&lt;T&gt; 对,就像过去的 OP 一样,您的回答清除了关于选择将它们作为一对实现的问题所提出的任何疑问。为此 +1。
  • 这正是我的问题——“无法优雅地实现无值结果”一方面是两种不同的类型,另一方面是这种无能
  • @isxaker:我的意思是这是你自己制造的问题。你一个很好的方法来处理它(IResult/Result 类型),然后你决定取消它们 - 没有任何具体的理由来解释你为什么要这么做那个决定。问题在于取消它们的决定。解决方案是不要取消它们。
  • @isxaker “一方面是两种不同的类型” 但是为什么会有这样的问题? 你显然认为它是,但是你不证明这个立场是合理的。这两个类的存在不是问题,它是一种解决方案,它提供了在某些情况下而不是在其他情况下返回值的能力,这正是您正在寻找的。如果不解释你认为这是一个问题的原因,就不可能探索你是如何得出你所做的结论以及错误是在哪里犯的。
  • "但是为什么会有问题" 我有一个入口点并且不想使用强制转换(甚至像is 这样的安全强制转换)。喜欢var respose = server.sendRequest(request); responce.EnsureSuccess(); //etc。我认为在没有 if else 的情况下使用一个代码分支会更容易,而且两种类型都没有不同的扩展。但我认为这是值得的。我想我会回到两种类型而不是一种。
【解决方案2】:

而且这段代码是不可编译的:

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

没有,也没有为Data 使用任何其他实现类;只能是IResult&lt;object&gt;:

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

这种方法可能适合您的需求。

当您创建Result&lt;object&gt; 的实例时,请不要设置Value。无论如何,该接口都是 get-only,因此它将是不可变的 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

【讨论】:

    【解决方案3】:

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

    话虽如此,我建议让你的类不可变:

    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&lt;T&gt; 类的实例:

    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");
    

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

    【讨论】:

    • var result = Result&lt;string&gt;.Fail(new StatusCode(1), "Failed to do something"); 这让我很困惑为什么string。事实上它可以是任何类型
    • @isxaker 更改为 Result&lt;SomeType&gt; 以更清晰。
    猜你喜欢
    • 2010-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多