返回空任务还是null更好?(针对c#)

29

我有一个异步方法,它将通过 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个回答

39
如果没有结果,返回空任务还是null更好呢?
这里有几件事情需要考虑:
首先,你应该永远不要返回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;
}

14
如果你真的想从异步方法返回null,你可以使用Task.FromResult(null)
例如:
public async Task<FileInfo> GetInfo()
{
    return await Task.FromResult<FileInfo>(null);
}

10

Stephen Cleary的回答解释得很好:不要返回null,否则将会引发null reference异常,但我想补充一些内容:

  • 如果你的函数返回一个Task,那么应该返回一个已完成的任务,可以通过返回Task.CompletedTask来实现
  • 如果你的函数返回一个Task<T>,那么应该返回一个已完成的T类型的任务,可以使用Task.FromResult<TResult>(TResult)来实现

如果你返回null而不是一个已完成的任务,这段代码将抛出null reference异常:

await FunctionThatShouldRetunrTaskButReturnsNull();

即使在调试器中查看代码,理解代码的含义也有些困难。

因此,在返回 Task 的非async函数中千万不要返回 null

解释:

  • 在返回 TaskTask<T> 的非async函数中,你需要显式地创建任务,并且有可能返回一个null 而不是一个任务。
  • 在返回 TaskTask<T> 的异步函数中,你只需返回或返回一个值,函数的结果会被隐式转换为任务,所以不会出现返回 null 的情况。

1
我个人的偏好是尽可能避免使用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
当返回一个集合时,我更喜欢返回一个空集合而不是一个空对象。这样需要较少的分支,使代码更易于阅读和测试。如果返回一个空的集合,你最终会得到类似这样的结果:
var results = await GetResultsForJobId(1234);
if(results != null) {}
// or results?.SomeLinqOperation();

用一个空集合,这很简单。
var results = await GetResultsForJobId(1234);
results.SomeLinqOperation();

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

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

变量jobId = api.GetJobRunIdAsync(1234).Result;将会取走异步调用,因为您正在请求结果而不是使用continueWith调用?这将同步运行。 - crayoyeah
@crayoyeah 这只是一个你可能称呼它的示例。重要的部分是返回 int? (Nullable<int>) 而不是 int。如果你想要 "真正" 的异步,你应该将方法签名写成 public async Task<int?> GetJobRunIdAsync(int jobId) - E. Moffat

0

0

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

如果集合对象为null而不是空集合,则foreach将遇到NullReferenceException。返回一个空集合只是为了避免由于忘记在该情况下进行空引用检查而导致程序崩溃,这样更直观。

此外,集合的对象有时只能在需要它们的时候才能创建,例如通过yield语句。在此之前,结果可能还不存在,因此null可能会有问题,因此在这种情况下返回一个空集合而不是null是有意义的。

然而,如果您返回单个对象,则如果表示对象的实体不存在并且可能与空对象不同(可能具有初始化为默认值等属性),则返回null是完全有效的。如果可能失败,您仍然需要检查是否失败,因此在这种情况下,null不是问题,实际上是期望的结果,如果没有任何引用可以表示那里没有东西。

关于这个Task。Task本身是必要的,用来检查或等待其完成。如果你需要确定Task已经完成,则返回一个空的Task。如果你不需要检查Task的完成情况,只是启动它然后就忘了,那么返回任何东西有什么意义呢?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接