一个接口和一个class方法的参数不人工检查怎么保证匹配?

How can I guarantee that an interface and a class method parameters match without manually checking?

我最近注意到一个错误,原因是 class 中的参数顺序与其接口参数顺序不匹配。我觉得这应该是一个编译错误。我发现接口不限制参数名称。因此,如果您在下面看到我的示例,我将 firstParametersecondParameter 颠倒了,我还有 parameterOneparameterTwo,它们都是有效构建。这可能构建得很好,但您可能 运行 进入 运行 时间错误。

另一方面,它确实需要参数类型的顺序匹配。所以我想也许我不需要在接口中放置参数名称而只需要放置值类型,但这不起作用并且接口仍然需要根据消费者放置的内容提供类型的描述那里。

我的问题 有没有办法在构建时保证 class 与接口参数名称匹配?我不想手动执行此操作。

这很重要的原因是即使方法变量不能设置为由接口使用,使用服务引用或接口的其他用途的人也会在设置时看到接口参数,即参数的预期用途。如果我不能准确地依赖合同,那么合同的意义何在?

interface IParameterTest
{
    void TwoStringParameters(string firstParameter, string secondParameter);

    void TwoStringParametersAndAnInt(string firstParameter, string secondParameter, int thirdParameter);
}

public class ParameterTest : IParameterTest
{
    //Builds and matches interface
    //public void TwoStringParameters(string firstParameter, string secondParameter)
    //{
    //  throw new NotImplementedException();
    //}

    //Builds and does not match interface
    //public void TwoStringParameters(string secondParameter, string firstParameter)
    //{
    //  throw new NotImplementedException();
    //}

    //Builds and does not match interface
    public void TwoStringParameters(string parameterOne, string parameterTwo)
    {
        throw new NotImplementedException();
    }

    //Builds and matches interface
    public void TwoStringParametersAndAnInt(string firstParameter, string secondParameter, int thirdParameter)
    {
        throw new NotImplementedException();
    }

    //Does not build or match interface
    //public void TwoStringParametersAndAnInt(int firstParameter, string secondParameter, string thirdParameter)
    //{
    //  throw new NotImplementedException();
    //}
}

Is there a way to guarantee at build time that a class matches the interface parameter names? I would prefer not to do this manually.

不在 C# 语言中。然而:

  • 您可以编写一个单元测试来检查,相当容易。构建时间不长,但仍然足够早,可以在错误成为大问题之前发现它们。
  • 您可以编写 Roslyn 代码诊断以将其标记为错误(甚至为其提供代码修复)。

当然,单元测试也可以通过 Roslyn 编写,但您只需使用普通反射即可相当轻松地完成。

从你的问题中不能完全清楚你是否发现了参数名称错误的真正令人讨厌的问题,顺便说一句 - 这不仅仅是在人类可读性方面,而且如果你使用命名,它会显着影响行为争论。例如,假设您有这样的代码:

public interface IFoo 
{
    void Foo(int x, int y);
}

public class FooImpl : IFoo
{
    public void Foo(int y, int x) { ... }
}

...
IFoo foo = new FooImpl();
foo.Foo(x: 10, y: 20); // Equivalent to foo.Foo(10, 20)

现在,如果有人决定改用 varfoo 的编译时类型会发生变化,因此命名参数会突然映射到不同的参数。

var foo = new FooImpl();
foo.Foo(x: 10, y: 20); // Equivalent to foo.Foo(20, 10)

完全有效的代码...但与之前的代码具有不同的含义。有时更改变量的编译时类型会影响事情,但是 通常 围绕重载等......这是在只有一种方法的简单情况下.

正如 Jon 所说,C# 并不真正关心调用的参数是什么,但是如果您想为自己反射性地断言参数名称;无论是在启动时还是在单元测试中,你都可以使用这样的东西:

public class Program
{
    public static void Main(string[] args)
    {
        var assembly = Assembly.GetAssembly(typeof(Program));

        var types = assembly
            .GetTypes()
            .Where(x => x.IsClass && x.GetInterfaces().Any());

        foreach (var type in types)
        {
            var interfaces = type.GetInterfaces().Where(x => x.Assembly == assembly);

            foreach (var iface in interfaces)
            {
                var classMethods = type.GetMethods();

                foreach (var interfaceMethod in iface.GetMethods())
                {
                    var classMethod = classMethods.First(x => x.ToString() == interfaceMethod.ToString());

                    Debug.Assert(
                        interfaceMethod.GetParameters().Select(x => x.Name).SequenceEqual(classMethod.GetParameters().Select(x => x.Name)),
                        "Incorrect parameter names in method: " + type.Name + "." + classMethod.Name);
                }
            }
        }
    }

    public interface ITest
    {
        void MethodA(string first, string second);
    }

    public class TestA : ITest
    {
        public void MethodA(string first, string second) { }
    }

    public class TestB : ITest
    {
        public void MethodA(string second, string first) { }
    }
}

是的,您有一些构建时的替代方案。您必须权衡是否值得付出努力。

  • 创建自定义静态代码分析规则(以前是 FxCop)
  • 使用 Roslyn 并将其插入 MSBuild
  • 插入到 MSBuild 中的完全自定义的东西,如果您没有匹配项,构建就会失败

你的问题

If I cannot rely on a contract to be exact, what is the point of a contract in the first place?

值得考虑,但是 所需参数的名称 作为合同要求可能被认为非常狭窄。有一种编译时方法可以解决这个问题,你甚至在你的问题中提出了它:你受参数的顺序、数量和类型的约束。如果你需要一个非常严格的接口,你可以通过包装简单类型来抽象参数,防止意外参数交换等情况。

同样,您必须权衡是否值得。您正在以更多代码和认知负荷为代价购买界面安全性。

FxCop already has a rule to enforce this. You can enable code analysis 到 运行 on build 在您的项目属性中,然后配置代码分析规则集以将该警告视为错误。如果将其集成到构建过程中,您将迫使所有开发人员在构建成功之前解决该问题。

在这种情况下,我建议一个更好的解决方案是重构接口以使用参数对象设计模式,而不是乱搞强制以正确的顺序实现两个字符串的参数名称。

例如,如果字符串 1 是 "firstName" 并且字符串 2 是 "lastName" 你最好使用具有两个属性 FirstName 和 SecondName 的 class 然后让你的界面依赖于此参数对象而不是低级数据类型,例如字符串。