开发者

How do I imply code contracts of chained methods to avoid superfluous checks while chaining?

开发者 https://www.devze.com 2022-12-29 02:16 出处:网络
I\'m using Code Contracts in C# 4.0. I\'m applying the usual static method chaining to simulate optional parameters (I know C# 4.0 supports optional parameters but I really don\'t want to use them).

I'm using Code Contracts in C# 4.0. I'm applying the usual static method chaining to simulate optional parameters (I know C# 4.0 supports optional parameters but I really don't want to use them).

The thing is that my contract requirements are executed twice (or possibly the number of chained overloads I'd implement) if I call the Init(string , string[]) method -- an obvious effect from the sample source code below. This can be expensive, especially due to relatively expensive requirements like the File.Exists I use.

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

    //...
}

If however, I remove the requirements from that method, the static checker complains that the requirements of the Init(string, string[], string) overload are not met. I reckon that the static checker doesn't understand that there requirements of the Init(string, string[], string) overload implicitly apply to the Init(string, string[]) method as well; something that would be perfectly deductable from the code IMO.

This is the situation I would like to achieve:

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

    //...
}

So, my question is this: is there a way to have the requirements of Init(string, string[], string) implicitly apply to Init(string, string[]) automatically?

Update

I was using the ForAll method in the wrong way: it is intended to use inside a requirement or alike, like this:

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


I don't think there is for the general case.

In your specific case, you could certainly move the more expensive ForAll(File.Exists) to the actual implementation method and get no warnings :

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

Edit - I would forget about doing it at the earlier levels and just decorate the methods with the ContractVerification() attribute. This gives me no warnings, 7 checked assertions, with all the static checking options turned on.

    [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)));


        // ... 
    }


Since you are using Contracts I assume you are using C# 4.0. Then you can use optional parameters instead and only have one place to put your contracts.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号