返回空任务或null是否更好? C#

时间:2017-07-27 09:49:14

标签: c# task task-parallel-library

我有一个异步方法,它将通过Api查找作业调度服务的jobId。

如果找不到结果,最好返回一个空任务还是null?

据我所知,当返回一个集合时,最好返回一个空集合而不是null并使用对象最好返回null而不是空对象;但对于任务我不确定哪个是最好的。 见附件。

谢谢

   public virtual Task<int> GetJobRunIdAsync(int jobId)
        {
            var jobMonRequest = new jobmonRequest(true, true, true, true, true, 
            true, true, true, true, true, true, true,
            true,
            true, true, true, DateTime.Today, jobId, null, 0, null, null,
            null, null, 0, 0);

        var jobMonResponseTask = Client.jobmonAsync(jobMonRequest);

        var jobTask = jobMonResponseTask.ContinueWith(task =>
        {
            if (jobMonResponseTask.Result == null )
            {
                var empty = new Task<int>(() => 0); // as i understand creating a task with a predefined result will reduce overhead.

                return empty.Result;   // || is it better to just return null?
            }
            if (jobMonResponseTask.Result.jobrun.Length > 1)
            {
                throw  new Exception("More than one job found, Wizards are abound.");
            }
              return jobMonResponseTask.Result.jobrun.Single().id;
        });

        return jobTask;
    }

6 个答案:

答案 0 :(得分:7)

  

如果找不到结果,最好返回一个空任务还是null?

这里有几件事需要考虑:

首先,你应该永远不会返回null Task 。在async世界中,null任务只是没有意义。 Task表示异步方法的执行,因此对于返回null任务的异步方法,就像告诉调用代码“你当然没有真正调用此方法”当然。

因此,从方法返回的Task / Task<T>永远不应该是null。但是,您仍然可以选择在常规任务中返回null 。这取决于你。

  

我不确定哪个是最好的任务。

任务只是一个包装器。底层逻辑仍然相同。想想这个方法如果是同步的,它会是什么样的;如果找不到任何内容,您的退货类型会为int并返回0,或者如果找不到任何内容,您的退货类型会为int?并返回null吗?在为同步方法做出选择之后,将其包装在Task<T>中以用于异步方法。

作为最后一点,我必须说:

您的方法可以大大简化:

public virtual async Task<int> GetJobRunIdAsync(int jobId)
{
  var jobMonRequest = ...;
  var jobMonResponse = await Client.jobmonAsync(jobMonRequest);
  if (jobMonResponse == null)
    return 0;
  if (jobMonResponse.jobrun.Length > 1)
    throw  new Exception("More than one job found, Wizards are abound.");
  return jobMonResponse.jobrun.Single().id;
}

或者,如果您想要返回null(非任务):

public virtual async Task<int?> GetJobRunIdAsync(int jobId)
{
  var jobMonRequest = ...;
  var jobMonResponse = await Client.jobmonAsync(jobMonRequest);
  if (jobMonResponse == null)
    return null;
  if (jobMonResponse.jobrun.Length > 1)
    throw  new Exception("More than one job found, Wizards are abound.");
  return jobMonResponse.jobrun.Single().id;
}

答案 1 :(得分:3)

Stephen Cleary 的回答完美地解释了这一点:永远不要返回 string?,否则会引发空引用异常,但我想补充一点:

如果返回 null 而不是已完成的任务,此代码将抛出 null 引用异常:

T

即使您在调试器中看到它,也很难理解发生了什么。

因此,从不,从不,从返回 await FunctionThatShouldRetunrTaskButReturnsNull(); null 函数返回 async

说明:

  • 在返回 Taskasync 的非 Task 函数中,您需要显式创建任务,并且存在返回 Task<T> 的危险一个任务。
  • 在返回 nullasyncTask 函数中,您只需返回或返回一个值,并且函数的结果被隐式转换为任务,因此有没有返回 Task<T> 的危险。

答案 2 :(得分:1)

如果您确实要从异步方法返回null,则可以使用Task.FromResult(null)

例如:

public async Task<FileInfo> GetInfo()
{
    return await Task.FromResult<FileInfo>(null);
}

答案 3 :(得分:0)

这里有两点。首先是没有有效结果的返回值。我会将返回类型更改为int?并为jobRun Id返回null,以向调用者指示没有有效值。

此处详细介绍了如何返回任务的另一部分If my interface must return Task what is the best way to have a no-operation implementation?

答案 4 :(得分:0)

我个人的偏好是尽可能避免null。这会强制调用者实现检查返回值并减少无意的NullReferenceException

我唯一一次使用null是为了返回值类型。可以为空的值类型提供HasValueValue属性,以便调用者可以执行以下操作:

var jobId = api.GetJobRunIdAsync(1234).Result; //note: highly recommend using async/await here instead of just returning a task
if(jobId.HasValue)
{
   var result = DoSomethingWithId(jobId);
   //continue processing...
}

我认为这会在您提供的示例中很有效,因为您将返回int

返回集合时,我更喜欢空集合而不是空对象。这需要更少的分支,这使代码更容易阅读和测试 - 如果返回null集合,您最终会得到类似的东西:

var results = await GetResultsForJobId(1234);
if(results != null) {}
// or results?.SomeLinqOperation();

空集合只是

var results = await GetResultsForJobId(1234);
results.SomeLinqOperation();

对于其他非集合引用类型,我建议实现Maybe<T>Optional<T>,它可以与Nullable<T>类似的方式与引用类型一起使用。可以在https://github.com/nlkl/Optional的GitHub上找到一个这样的实现的示例。更简单的版本可能是:

public struct Optional<T>
{
    private static readonly Optional<T> _readOnlyEmpty = new Optional<T>();
    public static Optional<T> Empty => _readOnlyEmpty;

    public T Value { get; }

    public bool HasValue { get; private set; }

    public Optional(T value)
        : this()
    {
        Value = value;
        HasValue = true;
    }

    public static implicit operator Optional<T>(T value)
    {
        return new Optional<T>(value);
    }

    public static implicit operator T(Optional<T> optional)
    {
        return optional.Value;
    }
}

答案 5 :(得分:0)

最好返回一个空集合,而不是null,因为集合通常会实现IEnumerable,因此将通过foreach(var item in collection)进行迭代。

如果集合对象是NullReferenceException而不是空集合,则Foreach将遇到null。返回一个空集合将避免程序崩溃,因为在这种情况下忘记了空引用检查,并且更简单。

有时,只有在需要时才能创建集合对象,例如通过yield语句。在此之前,结果根本就不存在,因此null可能在这里存在问题,因此返回空集合而不是null在这里是有意义的。

但是,如果返回单个对象,则返回null是完全有效的,如果对象所代表的实体可能不存在,并且可能与空对象(可能具有初始化为默认值的属性等)不同。如果它可能失败,你将需要检查它是否确实失败了,所以在这种情况下null不是问题,事实上如果引用可能没有任何表示,那么就是所希望的结果。

至于TaskTask本身是检查或等待其完成所必需的。如果您需要确保Task已完成,请返回空Task。如果你不需要检查Task完成并且只是解雇并忘记它,那么重新调整任何东西的目的是什么?