【问题标题】:CodeContracts and null checks verbosityCodeContracts 和 null 检查详细程度
【发布时间】:2012-07-31 19:23:07
【问题描述】:

我目前正在减少我的应用程序框架中的一些重复项,我想知道人们对以下扩展方法有何看法?

[EditorBrowsable(EditorBrowsableState.Never)]
public static class ThrowExtensions
{
    public static T ThrowIfNull<T>(this T instance) where T : class
    {
        Contract.Ensures(Contract.Result<T>() != null);

        if (instance == null)
        {
            throw new ArgumentNullException("instance", string.Format("Object reference of type '{0}' not set to an instance of an object.", typeof(T).FullName));
        }

        return instance;
    }
}

考虑以下示例。

我知道扩展一切不是一个好习惯,我应该尽可能避免它,但在这种情况下,它似乎很合理,虽然不漂亮。

protected bool Contains<TEntity>(TEntity entity) where TEntity : class
{
    Contract.Requires(entity != null);

    ObjectStateEntry entry;

    bool exist = ((IObjectContextAdapter)_context).ObjectContext.ThrowIfNull().ObjectStateManager.ThrowIfNull().TryGetObjectStateEntry(entity, out entry);

    return exist;
}

编辑:我也在考虑使用 Try() 更改 ThrowIfNull 或更适合此上下文的东西作为框架中的约定,但我真的很喜欢对此的看法,如果你有更好的选择我很高兴听到,谢谢!

更新:到目前为止,这就是我最终的结果。

namespace EasyFront.Framework.Diagnostics.Contracts
{
    using System;
    using System.ComponentModel;
    using System.Diagnostics.Contracts;

    [EditorBrowsable(EditorBrowsableState.Never)]
    public static class ContractsExtensions
    {
        /// <summary>
        ///     Calls the code wrapped by the delegate when the subject is not pointing to null, otherwise, <see
        ///      cref="ArgumentNullException" /> is thrown.
        /// </summary>
        /// <remarks>
        ///     Eyal Shilony, 01/08/2012.
        /// </remarks>
        /// <typeparam name="TSubject"> The type of the subject to operate on. </typeparam>
        /// <typeparam name="TResult"> The type of the result to return from the call. </typeparam>
        /// <param name="subject"> The subject to operate on. </param>
        /// <param name="func"> The function that should be invoked when the subject is not pointing to null. </param>
        /// <returns> The result of the invoked function. </returns>
        public static TResult SafeCall<TSubject, TResult>(this TSubject subject, Func<TSubject, TResult> func)
            where TSubject : class
        {
            Contract.Requires(func != null);

            if (subject == null)
            {
                ThrowArgumentNull<TSubject>();
            }

            return func(subject);
        }

        /// <summary>
        ///     Calls the code wrapped by the delegate when the subject is not pointing to null,
        ///     otherwise, <see cref="ArgumentNullException" /> is thrown.
        /// </summary>
        /// <remarks>
        ///     Eyal Shilony, 01/08/2012.
        /// </remarks>
        /// <typeparam name="TSubject"> The type of the subject to operate on. </typeparam>
        /// <param name="subject"> The subject to operate on. </param>
        /// <param name="func"> The function that should be invoked when the subject is not pointing to null. </param>
        public static void SafeCall<TSubject>(this TSubject subject, Action<TSubject> func)
            where TSubject : class
        {
            Contract.Requires(func != null);

            if (subject == null)
            {
                ThrowArgumentNull<TSubject>();
            }

            func(subject);
        }

        /// <summary>
        ///     Ensures that the subject is not pointing to null and returns it, otherwise, <see cref="ArgumentNullException" /> is thrown.
        /// </summary>
        /// <remarks>
        ///     Eyal Shilony, 01/08/2012.
        /// </remarks>
        /// <typeparam name="TSubject"> The type of the subject to operate on. </typeparam>
        /// <param name="subject"> The subject to operate on. </param>
        /// <returns> The subject. </returns>
        public static TSubject SafeReturn<TSubject>(this TSubject subject)
            where TSubject : class
        {
            Contract.Ensures(Contract.Result<TSubject>() != null);

            if (subject == null)
            {
                ThrowArgumentNull<TSubject>();
            }

            return subject;
        }

        private static void ThrowArgumentNull<TSubject>() where TSubject : class
        {
            // ReSharper disable NotResolvedInText
            throw new ArgumentNullException("subject", string.Format("Object reference of type '{0}' not set to an instance of an object.", typeof(TSubject).FullName));
            // ReSharper restore NotResolvedInText
        }
    }
}

这是我整理的优缺点列表。

优点:

  • 删除了复杂空检查的冗余。
  • 与 NullReferenceException 相比,提供有关错误的更多信息。
  • 一个集中位置,您可以控制错误的行为,例如在生产构建中,您可以决定要使用异常,而在调试构建中,您可以选择使用断言或记录传播的异常。
  • 代码可读性更强,更优雅。

缺点:

  • 性能?
  • 陡峭的学习曲线,尤其是对于那些不熟悉委托、lambda 运算符或函数式编程的人。
  • 清晰度降低。

这是我必须处理的一些代码。

var entity = Builder.Entity<User>();

entity.HasKey(u => u.Id);
entity.Property(u => u.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
entity.Property(u => u.DisplayName).IsRequired();
entity.Property(u => u.DisplayName).HasMaxLength(20);
entity.Property(u => u.Username).HasMaxLength(50);
entity.Property(u => u.Password).HasMaxLength(100);
entity.Property(u => u.Salt).HasMaxLength(100);
entity.Property(u => u.IP).HasMaxLength(20);
entity.Property(u => u.CreatingDate);
entity.Property(u => u.LastActivityDate);
entity.Property(u => u.LastLockoutDate);
entity.Property(u => u.LastLoginDate);
entity.Property(u => u.LastPasswordChangedDate);

【问题讨论】:

  • 我不喜欢这些。一点也不。你是在用清晰来换取简洁,这不是一个好的交易。
  • 我同意,这就是我发布它的原因,如果你必须链接调用并检查主题是否指向 null,你会怎么做?
  • 如果空检查,你会使用普通旧的吗?在像 LINQ 这样的长链调用中,这可能会变得非常混乱。
  • 您是否有一个类不变量在某处说_context 不能为空(正如您的代码所暗示的那样)?您可以添加一些额外的不变量来确保这些其他值不为空。您甚至可以考虑将ObjectStateManager 放在它自己的变量中是否有意义。如果不出意外,您可以编写一个返回 ObjectStateManager 的函数,并以明显的显式方式执行所有必要的 null 检查。
  • 由于每个级别的非空值都是预期的,因此它实际上是一个隐含的不变量,即使它在合同中没有这样说明。考虑到这一点,当违反该不变量时,您的代码实际上只是将 NullReferenceException 转换为 NullArgumentException。我真的不明白这能给你带来什么。

标签: c# generics nullreferenceexception code-contracts


【解决方案1】:

Jeffrey 制作的 cmets 很有价值,所以我只是忽略了使用 ContractVerificationAttribute 的警告。

【讨论】: