【问题标题】:How do I imply code contracts of chained methods to avoid superfluous checks while chaining?我如何暗示链接方法的代码合同以避免链接时多余的检查?
【发布时间】:2011-02-14 19:01:54
【问题描述】:

我在 C# 4.0 中使用代码协定。我正在应用通常的静态方法链接来模拟可选参数(我知道 C# 4.0 支持可选参数,但我真的不想使用它们)。

问题是,如果我调用Init(string , string[]) 方法,我的合同要求会执行两次(或者可能是我要实现的链式重载的数量)——从下面的示例源代码中可以明显看出效果。这可能很昂贵,尤其是由于我使用的File.Exists 等相对昂贵的要求。

public static void Init(string configurationPath, string[] mappingAssemblies)
{
    // The static contract checker 'makes' me put these here as well as
    // in the overload below.
    Contract.Requires<ArgumentNullException>(configurationPath != null, "configurationPath");
    Contract.Requires<ArgumentException>(configurationPath.Length > 0, "configurationPath is an empty string.");
    Contract.Requires<FileNotFoundException>(File.Exists(configurationPath), configurationPath);
    Contract.Requires<ArgumentNullException>(mappingAssemblies != null, "mappingAssemblies");
    Contract.Requires<FileNotFoundException>(Contract.ForAll<string>(mappingAssemblies, (n) => File.Exists(n)));

    Init(configurationPath, mappingAssemblies, null);
}

public static void Init(string configurationPath, string[] mappingAssemblies, string optionalArgument)
{
    // This is the main implementation of Init and all calls to chained
    // overloads end up here.
    Contract.Requires<ArgumentNullException>(configurationPath != null, "configurationPath");
    Contract.Requires<ArgumentException>(configurationPath.Length > 0, "configurationPath is an empty string.");
    Contract.Requires<FileNotFoundException>(File.Exists(configurationPath), configurationPath);
    Contract.Requires<ArgumentNullException>(mappingAssemblies != null, "mappingAssemblies");
    Contract.Requires<FileNotFoundException>(Contract.ForAll<string>(mappingAssemblies, (n) => File.Exists(n)));

    //...
}

但是,如果我从该方法中删除了要求,则静态检查器会抱怨不满足 Init(string, string[], string) 重载的要求。我认为静态检查器不理解 Init(string, string[], string) 重载的要求也隐式适用于 Init(string, string[]) 方法;可以从代码 IMO 中完全扣除的东西。

这是我想要达到的情况:

public static void Init(string configurationPath, string[] mappingAssemblies)
{
    // I don't want to repeat the requirements here because they will always
    // be checked in the overload called here.
    Init(configurationPath, mappingAssemblies, null);
}

public static void Init(string configurationPath, string[] mappingAssemblies, string optionalArgument)
{
    // This is the main implementation of Init and all calls to chained
    // overloads end up here.
    Contract.Requires<ArgumentNullException>(configurationPath != null, "configurationPath");
    Contract.Requires<ArgumentException>(configurationPath.Length > 0, "configurationPath is an empty string.");
    Contract.Requires<FileNotFoundException>(File.Exists(configurationPath), configurationPath);
    Contract.Requires<ArgumentNullException>(mappingAssemblies != null, "mappingAssemblies");
    Contract.ForAll<string>(mappingAssemblies, (n) => File.Exists(n));

    //...
}

那么,我的问题是:有没有办法让Init(string, string[], string) 的要求自动隐式应用于Init(string, string[])

更新

我以错误的方式使用了ForAll 方法:它打算在需求或类似内容中使用,如下所示:

Contract.Requires<FileNotFoundException>(Contract.ForAll<string>(mappingAssemblies, (n) => File.Exists(n)));

【问题讨论】:

  • 相关/重复:stackoverflow.com/questions/2539497/… 如果您想消除开销,您可以在发布版本中生成合同参考程序集并启用调用站点检查 - 请参阅文档了解更多详细信息。
  • 附带说明,File.Exists 对于合同条件来说是一个糟糕的选择,因为调用者不能静态地保证这个条件。这应该被记录为例外情况,但它不应该成为具有约束力的合同的一部分,因为它永远不能保证得到满足。
  • 谢谢,你说得对。更好的断言是使用正则表达式或其他方式在语法上验证路径。

标签: c# c#-4.0 code-contracts


【解决方案1】:

我认为一般情况下没有。

在您的具体情况下,您当然可以将更昂贵的 ForAll(File.Exists) 移至实际的实现方法并且不会收到警告:

        public static void Init(string configurationPath, string[] mappingAssemblies)
    {
        Contract.Requires<ArgumentNullException>(configurationPath != null, "configurationPath");
        Contract.Requires<ArgumentException>(configurationPath.Length > 0, "configurationPath is an empty string.");
        Contract.Requires<FileNotFoundException>(File.Exists(configurationPath), configurationPath);
        Contract.Requires<ArgumentNullException>(mappingAssemblies != null, "mappingAssemblies");
        Init(configurationPath, mappingAssemblies, null);
    }

    public static void Init(string configurationPath, string[] mappingAssemblies, string optionalArgument)
    {
        Contract.Requires<ArgumentNullException>(mappingAssemblies != null, "mappingAssemblies");
        Contract.ForAll<string>(mappingAssemblies, (n) => File.Exists(n));
    }

编辑 - 我会忘记在较早的级别执行此操作,而只是使用 ContractVerification() 属性装饰方法。这给了我没有警告,7 个检查断言,所有静态检查选项都打开了。

    [ContractVerification(false)]
    public static void Init(string configurationPath, string[] mappingAssemblies)
    {
        Init(configurationPath, mappingAssemblies, null);
    }

    public static void Init(string configurationPath, string[] mappingAssemblies, string optionalArgument)
    {

        Contract.Requires<ArgumentNullException>(mappingAssemblies != null, "mapping assemblies");
        Contract.Requires<ArgumentNullException>(configurationPath != null, "configurationpath");
        Contract.Requires<ArgumentException>(configurationPath.Length > 0, "configurationPath is an empty string");
        Contract.Requires<FileNotFoundException>(File.Exists(configurationPath));
        Contract.Requires<FileNotFoundException>(Contract.ForAll<string>(mappingAssemblies, (n) => File.Exists(n)));


        // ... 
    }

【讨论】:

  • 感谢您的建议,它确实似乎没有产生警告。我认为它必须在每个链式方法中。我刚刚发现普通的 File.Exists 具有生成警告的效果,而 ForAll(File.Exists) 没有。
  • 我发现以错误的方式使用 ForAll 方法(请参阅问题中的更新)。现在我已经更正了它,它给出了与其余要求相同的问题。
  • 答案已更新。最好不要在较早的级别尝试要求它,而是向下传播并在那里检查。
  • 谢谢,我想ContractVerification 属性很适合我。
  • 这个解决方案的问题是静态分析器不再认为Init(string,string[]) 有任何契约,并且例如如果任何代码使用空值调用此方法时不会警告您。 ContractVerification 应该仅在分析器无法正确理解您的合同或您正在使用大量未合同代码时使用。
【解决方案2】:

由于您使用的是合同,我假设您使用的是 C# 4.0。然后您可以改用可选参数,并且只有一个地方可以放置您的合约。

【讨论】:

    猜你喜欢
    • 2018-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-06
    • 1970-01-01
    相关资源
    最近更新 更多