【问题标题】:Casting a method to generic and non generic class将方法转换为泛型和非泛型类
【发布时间】:2018-07-03 14:01:59
【问题描述】:

我想具体一点。我有一个名为 Result 的类和一个派生类,名为 Result

public class Result
{
    public string Message { get; set; }

    public bool Success { get; set; }
}

public class Result<T> : Result
{
    public T Data { get; set; }
}

这些类在方法中用作返回类型。为此,我制作了这个助手类:

public class ResultManager
{
    /// <summary>
    /// Returns a success action
    /// </summary>
    public static Result Success()
    {
        var result = new Result { Success = true };

        return result;
    }

    /// <summary>
    /// Returns a success action
    /// </summary>
    /// <typeparam name="T">The object type to return with the result</typeparam>
    public static Result<T> Success<T>(T data)
    {
        var result = new Result<T> { Success = true, Data = data };

        return result;
    }

    /// <summary>
    /// Returns a failed result
    /// </summary>
    public static Result Failed(string errorMessage = null, Exception exception = null)
    {
        // ... unrelevant code for the question

        return new Result { Success = false, Message = errorMessage };
    }
}

如你所见,上面的代码有两种返回成功的类型,一种是你不想返回任何东西,另一种是你想返回一些东西,但失败的结果永远不会返回一些东西。这是不必要的,只是一个错误消息。 这给我带来了以下几点:当我想创建一个可以返回 Success 并返回类型的方法时,如果没有它,我如何转换它来确定返回类型是 Success(generic) 还是 Failed ? 喜欢这里的方法:

// The return type of the method is Result, because Result<T> derives from it
public static Result GetClipboardFromDateTime(DateTime dateTime)
{
    // Search for items
    IEnumerable<ClipboardCopy> items = null;
    try
    {
        items = mClipboardCollection.Where(a => a.CopiedTime == dateTime);
    }
    catch (Exception ex)
    {
        // This will return the class "Result"
        return ResultManager.Failed(
            "Something went wrong getting copies from the clipboard.", ex);
    }

    // If there is not values
    if (items.Count() <= 0)
        return ResultManager.Failed("There are not copies with that datetime.");

    // Return values
    // This will return the class Result<T>
    return ResultManager.Success(items.ToArray());
}

其实我就是这样做的:

var clipboard2 = (Result<ClipboardCopy[]>)AClipboard.GetClipboardFromDateTime(DateTime.Today);

如果返回类型是 Result(generic),这将起作用,但如果是 Return,它将崩溃并出现异常:

System.InvalidCastException: '无法将对象从类型 'AdvancedClipboard.Desktop.Models.Result' 转换为类型 'AdvancedClipboard.Desktop.Models.Result`1[AdvancedClipboard.Desktop.Models.ClipboardCopy[]]'。

【问题讨论】:

  • 崩溃时的错误信息是什么?
  • System.InvalidCastException: '无法将对象从类型 'AdvancedClipboard.Desktop.Models.Result' 转换为类型 'AdvancedClipboard.Desktop.Models.Result`1[AdvancedClipboard.Desktop.Models.ClipboardCopy[ ]]'。'
  • 基类Result的意义何在?如您所见,首先要做的就是将其转换为其他东西。当您将基类的实例转换为派生类的实例时,您会期待什么,而实际上它不是派生类? C# 设计者决定抛出一个异常(你已经向我们展示了),认为这是一个程序员错误。
  • @ZdeněkJelínek 是的,但第二个结果:“没有那个日期时间的副本。”不是编程错误。
  • 为什么不总是返回一个Result&lt;ClipboardCopy[]&gt;?在某些情况下,您可能没有任何数据,但这不应该给您带来问题......

标签: c# generics


【解决方案1】:

您正在尝试创建类似于 Option / Maybe monad 的东西:) 您可以考虑的选项很少:

  1. 把所有东西都放在“父”中

    class Result<TValue>
    {
      TValue Value // throws if !HasValue
      bool HasValue 
      string ErrorMessage // throws if HasValue
    }
    

您可以使用派生类型 Some, None (Success, Failure) 与适当的构造函数,或父类型上的静态“创建”方法 + 私有构造函数,或扩展方法 - 无论您喜欢创建“成功”, “失败”着。返回此 Result 的函数将始终需要指定泛型参数。

  1. 你所拥有的 + “模式匹配” = “类型切换” = 在调用方进行转换 即

    var result = GetClipboardFromDateTime();
    switch(result)
    {
      case Success<ClipboardCopy[]>: ... break;
      case Failure: ... break;
      default: throw
    }
    

(C# 7 特性),或者

    if (result is Success<ClipboardCopy[]>)
    (result as Success<ClipboardCopy[]>)?.Value 

等等

  1. 将成功/错误延续/回调放在 Result 类中

    Action<TValue> / Func<TValue, ?> onSuccess
    Action<string> / Func<string, ?> onError
    

    result.OnSuccess(value => /* do smthn /w value */);
    result.OnError(errorMsg => /* do smthn /w error msg */);
    

    (编辑)结果实现:

    public void OnSuccess(Action<TValue> onSuccess) {
      if (!_hasValue) return;
      onSuccess(_value);
    }
    
    public void OnError(Action<string> onError) {
      if (_hasValue) return;
      onError(_errorMessage);
    }
    
    // alternatively
    public void Apply(Action<TValue> onSuccess, Action<string> onError) {
      if (_hasValue) {
        onSuccess(_value);
        return;
      }
      onError(_errorMessage);
    }
    

    父 Result 将需要具有这些方法。可以通过各种方式完成,(父 impl 可以“什么都不做” - 成功和失败子级将覆盖而不检查 _hasValue 等)。 OnSuccess()、OnError()、Apply() 不必为 void,您可以使用 Result&lt;TResult&gt; OnSuccess&lt;TResult&gt;(Func&lt;TValue, TResult&gt; onSuccess)(而不是 void + Action&lt;&gt;)来链接它们(我不会这样虽然在 C# 中:D)。

一般来说,在 C# 中,我会避​​免以这种方式从方法中“返回异常”/“错误代码”等 - 我相信您应该实现“快乐路径”并通过异常完成异常处理(如果可能) .不过,我真的很喜欢在 TryGet() 方法上使用这种“模式”。

【讨论】:

  • 这是一个很好的答案。另外,你能解释一下延续/回调吗?我试图做到,但我做不到。也许是一个链接?
  • 已编辑我的答案。您可能有兴趣搜索“C# Maybe monad”、“C# Option monad”,看看有什么可能
  • 非常好的帖子。我不明白为什么它的票数与我的相差如此之远。
  • 我刚刚发现了一个 very nice post 讨论类似的话题(提出一个问题,为什么不直接使用 FP 语言:D)
【解决方案2】:

您尝试预先转换时出现问题。方法返回两种类型的结果。

  1. 成功
  2. 失败

我认为你应该这样做。

var result = AClipboard.GetClipboardFromDateTime(DateTime.Today);

if(Result.Success)
{
   var dataResult = (Result<ClipboardCopy[]>) result;
}

如果您不想进行强制转换。还有另一种方法,但我不建议。

var result = AClipboard.GetClipboardFromDateTime(DateTime.Today);

if(result.Success)
{
    dynamic d = result.Data;
}

以上示例动态您不需要进行强制转换,但同时您松​​散了对数据的强类型访问。主要建议在您进行互操作工作时使用。如果您同意失去强类型访问权限,那么您可以使用它。还有一件事,与此相关的任何错误都将在运行时执行。

【讨论】:

  • 你能解释一下为什么不建议使用“动态”吗?
  • 第二个选项真的没用,除非你去掉if语句。
  • 如果不检查是否这样做,它将在运行时失败,因为返回失败结果时它没有数据。
  • 在这种情况下,我会说不要使用动态,因为您应该知道结果的类型,所以投射会更可取。
  • 这就是为什么我给出了一种可能的解决方案,但我不建议这样做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-09
  • 2021-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多