代码之家  ›  专栏  ›  技术社区  ›  isxaker

如何实现空类型的泛型接口?

  •  0
  • isxaker  · 技术社区  · 6 年前

    我有以下代码:

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

    但我正在努力解决 T empty/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; }
    }
    
    0 回复  |  直到 6 年前
        1
  •  2
  •   Flater    6 年前

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

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

    从本质上讲,你的论点与说:

    我不喜欢有一个基类和一个派生类,所以我只需要生成派生类,而不需要使用某些属性。

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

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


    我怀疑你可能掩盖的是 Result<T> 可以继承自 Result 以及 实施 IResult<T> 接口:

    // other interfaces/classes are unchanged
    
    public class Result<T> : Result, IResult<T>
    {
        public T Value { get; set; }
    }
    

    这允许您 重新使用 一些 后果 处理时的逻辑 结果(<);T> 对象,在不需要与潜在返回值交互的情况下。

    请注意,这反映了类似的用例,例如 Task Task<T> 异步代码中的返回类型。这些类型不同,因为它们的处理方式不同。但是,任何 任务(<);T> 可以像对待其他人一样对待 任务 自从 Task<T> : Task


    如果一个结果有一个值,那么该值将在那里,对于我没有任何值的情况,该值将为null。

    这只是请求空引用异常。有有效的案例 null ,但我强烈建议不要创造更多的写作理由 无效的 进行必要的检查。

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

    由于给定方法是返回无值结果还是返回值结果这一事实无论如何都是在方法体中硬编码的,因此让方法的返回类型也实际反映这一点不会有任何损失。

    这是一个向您的消费者明确他们可以从您的方法中得到什么的问题。通过合并结果类型,您可以使其变得不清晰,从而不必要地混淆接口。


    还要注意的是,我并不认为有必要 IResult / IResult<T> 当您处理本质上是值对象的内容时,这里的接口,但由于您的问题是基于上下文的,因此可能有一些不可告人的原因,需要使用这些接口,而这些接口在这个问题中并不明显。

        2
  •  1
  •   Johnathan Barclay    6 年前

    此代码不可编译:

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

    否,也不使用任何其他实现类 Data ;它只能是 IResult<object> :

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

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

    创建的实例时 Result<object> 那就不要设定 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
  •  1
  •   Zohar Peled    6 年前

    空值的问题是值类型不能为空。
    您可以通过使用 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<T> 类别:

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

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

    推荐文章