Согласованность с необязательными значениями параметров

Я уже некоторое время ограниченно использую необязательные параметры в .NET 4. Недавно я подумал о том, как реализованы необязательные параметры, и меня осенило.

Возьмем следующий пример:

public interface ITest
{
    void Go(string val = "Interface");
}

Единственное требование для реализации этого состоит в том, чтобы была реализована самая большая форма подписи void Go (string val), на самом деле нам не нужно реализовывать необязательный параметр, поскольку компилятор позаботится об этом подключении (хотя возможность использования необязательных параметров будет недоступна, когда используя конкретный класс напрямую).

С учетом сказанного, чтобы обеспечить функциональность в обоих местах и ​​чтобы ее можно было обнаружить в реализации, можно реализовать необязательное объявление как в интерфейсе, так и в реализации. Фактически, инструмент повышения производительности ReSharper автоматически подтянет это необязательное объявление к конкретному типу при реализации интерфейса. Это кажется логичным поступком. Тем не мение...

Что мешает нам использовать разные значения в конкретном типе и интерфейсе? Это вызвало у меня тревогу сегодня утром, как будто кто-то войдет, чтобы изменить это значение, и забудет сохранить его в наследовании переопределений/интерфейсов, поведение будет совершенно другим в зависимости от того, как вы получаете доступ к объекту. Это может быть очень неприятно для отладки.

Попробуйте этот скрипт LINQpad:

void Main()
{
    IA iface = new A();
    A cls = new A();

    iface.Go();
    cls.Go();
}

interface IA
{
    void Go(string val = "Interface");
}

class A : IA
{
    public void Go(string val = "Class") { val.Dump(); }
}

Вывод будет:

Interface
Class

Что подводит меня к моему актуальному вопросу:

Вопрос:

Каким (если есть?) способом мы можем защитить это, не теряя возможности использовать необязательный вариант параметра из конкретного класса, чтобы он был доступен для обнаружения/чтения кодировщику?

Кто-нибудь сталкивался с этой проблемой раньше? Как вы это решили? Существуют ли какие-либо передовые методы, которые могут помочь предотвратить распространение этой проблемы в крупномасштабной кодовой базе с несколькими разработчиками?


person Aren    schedule 17.10.2012    source источник


Ответы (2)


Ничто не мешает вам это сделать. Использование необязательных параметров запекает значение в вызове метода. Итак, как вы видите, вызовы IA.Go() и A.Go() дают разные значения, потому что они скомпилированы в IA.Go("Interface") и A.Go("Class").

Обойти это (то есть указать в реализации только параметр "по умолчанию") можно с помощью overload вместо необязательных параметров:

interface IA
{
    void Go();
    void Go(string val);
}

public class A : IA
{
    public void Go()
    {
        Go("Class");
    }
    public void Go(string val) { val.Dump(); }
}

Некоторые другие варианты:

  • Добавьте модульный тест, который проверяет это условие. Однако вам придется делать это для каждой реализации.
  • Исследование создания собственное правило анализа кода.
person D Stanley    schedule 17.10.2012
comment
Я знаю, как это работает, мне было любопытно, придумал ли кто-нибудь обходной путь, который не был бы таким драконовским, как не использовать их. Знаете ли вы какой-либо способ отключения предупреждения или какой-либо передовой опыт, который может помочь снизить риски? - person Aren; 17.10.2012
comment
Вы можете добавить модульный тест, который проверяет это условие. Вам придется делать это для каждой реализации. Вы также можете изучить создание пользовательского правила анализа кода. - person D Stanley; 17.10.2012
comment
+1 Шаг статического анализа кода может быть хорошим способом обеспечить это. - person Aren; 17.10.2012
comment
Ваш комментарий почти более ценен, чем ваш ответ :) очень хороший комментарий (и вы должны добавить его к своему ответу, поскольку он действительно повышает ценность). - person Styxxy; 18.10.2012

Я думаю, что это природа C#, и вам придется с этим смириться. Посмотрите на пример ниже. Делает то же самое без необязательных параметров, т.е. поведение отличается в зависимости от того, как вы получаете доступ к своему объекту.

class Program
{
    static void Main(string[] args)
    {
        IA iface = new A();
        A cls = new A();

        iface.Test();
        cls.Test();

        Console.ReadKey();
    }
}

interface IA
{
    void Test();
}

class A : IA
{
    public void Test()
    {
        Console.WriteLine("Class");
    }

    void IA.Test()
    {
        Console.WriteLine("Interface");
    }
}
person Adam Kostecki    schedule 17.10.2012
comment
Я думал об этом, но проблема с этим сравнением заключается в том, что тот, кто реализует A, явно вызывает другое поведение по какой-либо причине. В примере с ОП это может быть совершенно непреднамеренно. - person Bobson; 17.10.2012
comment
@Bobson: Именно, случайное изменение - это то, от чего я пытаюсь защититься. - person Aren; 17.10.2012
comment
Причина в том, что необязательные параметры — это не более чем синтаксический сахар. Во время компиляции компилятор вставит соответствующее значение в место вызова на основе статического типа, а не типа времени выполнения. Я бы рекомендовал не использовать необязательные аргументы в интерфейсах (контрактах). Если вам нужен необязательный аргумент, изучите метод расширения, который предоставляет значение. Тогда это применимо ко всем реализациям в равной степени. - person mhand; 11.12.2016