代码合同和继承问题,究竟发生了什么?

时间:2012-01-12 23:34:42

标签: c# code-contracts

我可能会误解代码合同,但这是我的情况。

我有以下代码:

interface IFetch<T>    // defined in another DLL
{
    T Fetch(int id);
}

interface IUserFetch : IFetch<User>
{
    IEnumerable<User> GetUsersLoggedIn ();
}

class UserFetch : IUserFetch
{
    public User Fetch(int id)
    {
        return (User) Database.DoStuff (id);
    }

    public IEnumerable<User> GetUsersLoggedIn ()
    {
        return (IEnumerable<User>) Database.DoMoreStuff ();
    }
}

我正在尝试添加一个相对简单的合同:Contract.Requires (id != 0);,我想在Fetch上验证它。当我将其直接添加到Fetch时,我收到Method Fetch(int id) implements interface 3rdParty.IFetch<User> and thus cannot add Requires

的警告

我创建了一个实现IFetch的抽象代码契约类,并分别使用ContractClassContractClassFor属性将其指向/从UserFetch。我仍然收到像CodeContracts: The class 'FetchUserContracts' is supposed to be a contract class for '3rdParty.IFetch<User>', but that type does not point back to this class.这样的错误但是,由于3rdParty.IFetch是一个泛型类型,我认为我不能专门为它设置代码合同。

问题是否已经明确,如果是,我该如何解决?

2 个答案:

答案 0 :(得分:10)

您需要创建一个抽象类来实现合同,例如:

[ContractClassFor(typeof(IFetch<>))]
public abstract class ContractClassForIFetch<T> : IFetch<T>
{
    public T Fetch(int id)
    {
        Contract.Requires(id != 0);
        return default(T);
    }
}

并将以下ContractClass属性添加到IFetch:

[ContractClass(typeof(ContractClassForIFetch<>))]
public interface IFetch
{
    T Fetch(int id);
}

答案 1 :(得分:5)

我相信ƉiamondǤeezeƦ的回答是正确的。我只想补充一点解释。

您无法在封闭构造类型(例如Contract.Requires)中为方法添加IFetch<User>修饰。您必须将其添加到开放构造类型(IFetch<>)。原因与您无法将Contract.Requires修饰添加到用于实现接口的具体方法完全相同:代码契约在编译时可以在完整类型的对象实例时验证可能不得而知。

假设它确实允许您将合同放在具体的方法实现上。

public User Fetch(int id) 
{ 
    Contract.Requires (id != 0);, 
    return (User) Database.DoStuff (id); 
} 

现在假设有人试图使用接口类型的变量。

class DataDisplayer<T>
{
    Label myLabel = new Label();
    public void Display(IFetch<T> fetch, int id)
    {
        myLabel.Text = fetch.Fetch(id).ToString();
    }
}

这是否允许合同违规?这是不可能的,因为我们不知道fetch的具体类型将在运行时。

将合约放在特定的封闭构造类型IFetch<User>上并不能解决这个问题。我们仍然不知道调用代码中的T是什么类型。

Liskov替换原则是所有面向对象编程的基石,意味着我们永远不能假设某个对象的运行时类型超出了变量类型所指示的内容。由于变量的类型可能是通用接口,因此代码契约不会给调用者带来任何额外的负担,而不是通用接口定义中的负担。

因此,必须将Contract.Requires置于开放构造类型上,以便实现该接口的任何对象都具有该要求,并且可以验证通过接口类型的变量对该方法的任何可能调用关于该要求的正确性。