为什么我仍然获得代码合同:确保未经证实的警告?

时间:2010-11-18 22:32:05

标签: c# code-contracts design-by-contract

下面是一个非常简单的例子。当我打开静态分析警告时,我仍然可以得到 警告CodeContracts:确保未经证实:Contract.Result()!= string.Empty

就行了

  

返回string.Format(“{0},{1}”,   movie.Title,movie.Description);

请参阅我的以下代码

namespace CodeContractsSamples
{
    public class MovieRepo
    {
        public string GetMovieInfo(Movie movie)
        {
             Contract.Requires(movie != null);
             Contract.Ensures(Contract.Result<string>() != string.Empty);

             return string.Format("{0}, {1}", movie.Title, movie.Description);
         }
     }

      public class Movie
      {
         public string Title { get; set; }
         public string Description { get; set; }
      }
}

有什么想法吗?

2 个答案:

答案 0 :(得分:8)

这是mscorlib dll中Contracts实现的限制。

请参阅官方代码合同论坛上的this link

  这是因为合同   string.Format不保证它的   结果是非空的,只有它是   非空。

修改支持此功能的一些证据: 在mscorlib.Contracts.dll上使用Reflector时,可以看到在String.Format上定义的合约

[Pure, Reads(ReadsAttribute.Reads.Nothing)]
public static string Format(string format, object[] args)
{
    string str;
    Contract.Requires((bool) (format != null), null, "format != null");
    Contract.Requires((bool) (args != null), null, "args != null");
    Contract.Ensures((bool) (Contract.Result<string>() != null), null, "Contract.Result<String>() != null");
    return str;
}

如您所见,Contract.Result语句只是非空的,不是非空的。

答案 1 :(得分:1)

代码合同正确报告此问题。它无法证明返回的字符串不会为空。

真正的问题不在于Code Contracts的静态分析器。这是因为您没有提供足够的合同来确保此方法不会返回空字符串。

但是,您可以修复此方法,以便您可以向自己证明此方法不会返回空字符串,同时还可以帮助静态分析器。

  

注意
  下面的代码中有很多注释 - 所以它看起来比现在更长。但是,希望你能更好地理解代码合约能做什么和不能做什么。

     

此外,@ kenmetsu对String.Format(string, params object args)的代码合同也是正确的。然而,尽管如此,我在OP的代码中看到了一些问题,我觉得他们应该保证这个答案。这在以下代码的评论中阐明。

namespace CodeContractsSamples
{
    public class MovieRepo
    {
        [Pure]
        public string GetMovieInfo(Movie movie)
        {
            Contract.Requires(movie != null && 
                              !(string.IsNullOrEmpty(movie.Title) || 
                                string.IsNullOrEmpty(movie.Description)));
            // This ensures was always invalid. Due to your format
            // string, this string was guaranteed to never be empty--
            // it would always contain at least ", ".
            //Contract.Ensures(Contract.Result<string>() != string.Empty);
            Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()));

            // Changed this line to use C# 6 -- see the below
            //return string.Format("{0}, {1}", movie.Title, movie.Description);

            // Help out the static analyzer--and yourself! First, we prove that
            // Title and Description are greater than 0.
            // These asserts aren't strictly needed, as this should
            // follow from the pre-conditions above. But, I put this
            // here just so you could follow the inference chain.
            Contract.Assert(movie.Title.Length > 0);
            Contract.Assert(movie.Description.Length > 0);

            // Introduce an intermediate variable to help the static analyzer
            var movieInfo = $"{movie.Title}, {movie.Description}";

            // The static analyzer doesn't understand String.Format(...) 
            // (or string interpolation). However, above, we asserted that the movie 
            // Title's and Description's length are both greater than zero. Since we
            // have proved that, then we can also prove that movieInfo's length is
            // at least Title.Length + Description.Length + 2 (for the ", ");
            // therefore, we know it's not null or empty.
            // Again, the static analyzer will never be able to figure this out on
            // it's own, so we tell it to assume that that's true. But, again, you
            // have proven to yourself that this is indeed true; so we can safely tell
            // the analyzer to assume this.
            // If this seems like a lot of bloat--this is the cost of provable
            // code--but you can rest assured that this method will always work the
            // way you intended if the stated pre-conditions hold true upon method
            // entry.
            Contract.Assume(!string.IsNullOrEmpty(movieInfo));
            return movieInfo;
        }
    }

    // Since this class contained only data, and no behavior,
    // I've changed this class to be an immutable value object.
    // Also, since the class is immutable, it's also pure (i.e.
    // no side-effects, so I've marked it as [Pure].
    [Pure]
    public class Movie : IEquatable<Movie>
    {
        private readonly string _title;
        private readonly string _description;

        [ContractInvariantMethod]
        [System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Performance", 
            "CA1822:MarkMembersAsStatic", 
            Justification = "Required for code contracts.")]
        private void ObjectInvariant()
        {
            Contract.Invariant(!(string.IsNullOrWhiteSpace(_title) 
                && string.IsNullOrWhiteSpace(_description)));
        }

        public Movie(string title, string description)
        {
            // NOTE: For Code Contracts 1.9.10714.2, you will need to
            //       modify the Microsoft.CodeContract.targets file located
            //       at C:\Program Files (x86)\Microsoft\Contracts\MSBuild\v14.0
            //       (for VS 2015) in order for Code Contracts to be happy
            //       with the call to System.String.IsNullOrWhiteSpace(string)
            //       See this GitHub issue: 
            //           https://github.com/Microsoft/CodeContracts/pull/359/files
            Contract.Requires(!(string.IsNullOrWhiteSpace(title) &&
                                string.IsNullOrWhiteSpace(description)));
            Contract.Ensures(_title == title && 
                             _description == description);

            _title = title;
            _description = description;
        }

        public string Title => _title;
        public string Description => _description;

        // Since this class is now an immutable value object, it's not
        // a bad idea to override Equals and GetHashCode() and implement
        // IEquatable<T>
        public override bool Equals(object obj)
        {
            if (obj == null || !this.GetType().Equals(obj.GetType()))
            {
                return false;
            }

            Movie that = obj as Movie;
            return this.Equals(that);
        }

        public override int GetHashCode()
        {
            // Because we know _title and _description will
            // never be null due to class invariants, tell
            // Code Contracts to assume this fact.
            Contract.Assume(_title != null && _description != null);
            return _title.GetHashCode() ^ _description.GetHashCode();
        }

        public bool Equals(Movie other)
        {
            if (other == null)
            {
                return false;
            }

            return this._title == other._title &&
                   this._description == other._description;
        }

        public static bool operator == (Movie movie1, Movie movie2)
        {
            if (((object)movie1) == null || ((object)movie2) == null)
            {
                return object.Equals(movie1, movie2);
            }

            return movie1.Equals(movie2);
        }

        public static bool operator != (Movie movie1, Movie movie2)
        {
            if (((object)movie1) == null || ((object)movie2) == null)
            {
                return !object.Equals(movie1, movie2);
            }

            return !movie1.Equals(movie2);
        }
    }
}

一旦我通过Code Contracts静态分析器运行上面的代码,我就没有警告。