代码合同和继承问题,究竟是什么?

Bry*_*her 8 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 ();
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在尝试添加一个相对简单的合同: 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是一个泛型类型,我不认为我可以专门在它上面放一个代码契约.

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

Ɖia*_*zeƦ 10

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

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

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

[ContractClass(typeof(ContractClassForIFetch<>))]
public interface IFetch
{
    T Fetch(int id);
}
Run Code Online (Sandbox Code Playgroud)


Jef*_*dge 5

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

您无法Contract.Requires在封闭的构造类型(例如IFetch<User>)中向方法添加装饰.您必须将它添加到open构造类型(IFetch<>).原因与您无法将Contract.Requires装饰添加到用于实现接口的具体方法完全相同:代码契约在编译时可以在完整类型的对象实例未知时进行验证.

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

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

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

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

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

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

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

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