【问题标题】:Is it better to return an empty task or null? c#返回一个空任务还是返回null更好? C#
【发布时间】:2018-01-02 23:15:24
【问题描述】:

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

【问题讨论】:

    标签: c# task task-parallel-library


    【解决方案1】:

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

    如果您返回 null 而不是完成的任务,则此代码将引发 null 引用异常:

    await FunctionThatShouldRetunrTaskButReturnsNull();
    

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

    所以,永远,永远,从返回Taskasync函数返回null

    解释:

    • 在返回TaskTask&lt;T&gt; 的非async 函数中,您需要显式创建任务,并且存在返回null 而不是任务的危险。
    • 在返回TaskTask&lt;T&gt;async 函数中,您只需返回或返回一个值,函数的结果会隐式转换为任务,因此返回@987654340 没有危险@。

    【讨论】:

      【解决方案2】:

      如果你真的想从异步方法返回null,你可以使用Task.FromResult(null)

      例如:

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

      【讨论】:

        【解决方案3】:

        如果没有找到结果,是返回一个空任务还是返回null更好?

        这里有几件事需要考虑:

        首先,您应该永远不要返回 null Task。在async 的世界中,null 任务是没有意义的。 Task 代表异步方法的执行,所以异步方法返回null 任务就像告诉调用代码“你并没有真的只是调用这个方法”,当然它确实调用了。

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

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

        任务只是一个包装器。底层逻辑还是一样的。想想如果这个方法是同步的,它会是什么样子;如果什么也没找到,你的返回类型是int 并返回0,或者如果什么也没找到,你的返回类型是int? 并返回null?在为同步方法做出选择后,将其包装在 Task&lt;T&gt; 中以用于异步方法。

        最后,我必须说:

        您的方法可以大大简化:

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

        【讨论】:

          【解决方案4】:

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

          如果集合对象是null 而不是空集合,Foreach 将遇到NullReferenceException。返回一个空集合只会避免由于在这种情况下忘记空引用检查而导致程序崩溃,并且更直观。

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

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

          至于TaskTask 本身是检查或等待其完成所必需的。如果您需要确保 Task 已完成,则返回一个空的 Task。如果您不需要检查 Task 的完成情况而只是触发并忘记它,那么重新调整任何内容的目的是什么?

          【讨论】:

            【解决方案5】:

            我个人的偏好是尽可能避免使用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&lt;T&gt;Optional&lt;T&gt;,它们可以与Nullable&lt;T&gt; 类似的方式与引用类型一起使用。可以在 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;
                }
            }
            

            【讨论】:

            • var jobId = api.GetJobRunIdAsync(1234).Result;当您要求结果而不使用 continueWith 调用时,这不会取消异步调用吗?因为它将同步运行
            • @crayoyeah 这只是您如何称呼它的一个例子。重要的部分是通过 int 返回 int? (Nullable&lt;int&gt;)。如果你想要“真正的”异步,你应该将方法签名写为public async Task&lt;int?&gt; GetJobRunIdAsync(int jobId)
            【解决方案6】:

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

            关于如何返回一个Task的其他部分在这里详细介绍If my interface must return Task what is the best way to have a no-operation implementation?

            【讨论】:

              猜你喜欢
              • 2011-05-21
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-12-30
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多