Как я могу обернуть метод с необязательными параметрами в С#?

У меня есть следующий класс С# (упрощенный здесь):

internal static class Assertions {
    public static void Assert(bool condition, string message = "Assertion failed") {
        if (!condition) { throw new System.Exception(message); }
    }

    public static void NotNull(object o, string message = "Assertion failed") {
        Assert(!Object.ReferenceEquals(o, null), message);
    }

    public static void EtCaetera(..., string message = "Assertion failed") {
        Assert(..., message);
    }
}

Как видите, у меня есть метод Assertions.Assert() с необязательным параметром string message = "Assertion failed".

Когда я пишу оболочку вокруг этого метода, я бы хотел, чтобы у оболочки был параметр по умолчанию string message, но я хотел бы избежать повторения значения по умолчанию ("Assertion failed"), потому что это нарушает принцип DRY: если я хочу изменить сообщение "Assertion failed" на "I crashed", мне придется изменить это значение по умолчанию во многих местах.

Как я могу передать «отсутствие» необязательного параметра? Я ищу что-то вроде:

public static void NotNull(object o, string message = Type.Missing) {
    Assert(!Object.ReferenceEquals(o, null), message);
}

Другим вариантом было бы не использовать необязательные параметры и предоставить две версии каждого метода, но это быстро стало бы громоздким.


person Suzanne Soy    schedule 20.12.2013    source источник
comment
Возможно, строка const... ?   -  person Simon Whitehead    schedule 20.12.2013
comment
@SimonWhitehead Это отличная идея, я не знаю, почему я не подумал об этом.   -  person Suzanne Soy    schedule 20.12.2013


Ответы (3)


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

Мое предложение, если вы не хотите повторяться, состоит в том, чтобы ввести специальное значение (для имитации того, что такое Type.Missing):

internal static class Assertions {
    public static void Assert(bool condition, string message = null) {
        if (!condition) {
            throw new System.Exception(message ?? "Assertion failed");
        }
    }
}

internal static class Wrapper {
    public static void Assert(bool condition, string message = null) {
        Assertions.Assert(condition, message);
    }
}

У этого есть еще одно (ИМО большое) преимущество: если вы измените свое сообщение об ошибке (или вы локализируете его), вам не придется менять весь свой код (и существующий скомпилированные библиотеки будут обновлены). Не забывайте, что в исходном коде такой вызов:

Assertions.Assert(value > 0);

Будет переведено (и скомпилировано, даже если вы используете поле const) в:

Assertions.Assert(value > 0, "Assertion failed");

Таким образом, даже если вы измените сообщение по умолчанию, скомпилированные сборки не будут обновлены.

person Adriano Repetti    schedule 20.12.2013
comment
Обратите внимание, что вы можете упростить это, используя оператор объединения (??), т.е. message ?? "Assertion failed". - person Luaan; 20.12.2013

Я предпочитаю использовать null в качестве значения по умолчанию для необязательных аргументов.

internal static class Assertions {
    private const string DefaultMessage = "Assertion failed";

    public static void Assert(bool condition, string message = null) {
        message = message ?? DefaultMessage;
        if (!condition) { throw new System.Exception(message); }
    }

    public static void NotNull(object o, string message = null) {
        message = message ?? DefaultMessage;
        Assert(!Object.ReferenceEquals(o, null), message);
    }

    public static void EtCaetera(..., string message = null) {
        message = message ?? DefaultMessage;
        Assert(..., message);
    }
}
person Justin    schedule 20.12.2013

Значение по умолчанию должно быть указано в первом вызываемом методе (т. е. в вашей оболочке), так как именно здесь значение применяется к параметру. Type.Missing — это специальное значение, имеющее смысл в COM-взаимодействии. Вот несколько вариантов, которые вы можете попробовать, которые могут удовлетворить ваши потребности.

  1. Используйте OptionalAttibute в базовом методе и укажите значение по умолчанию для переопределенного метода.

    class Class2 : Class1
    {
        public override string MethodWithOptParams(string message = "default message")
        {
            return base.MethodWithOptParams(message);
        }
    }
    
    class Class1
    {
        public virtual string MethodWithOptParams([Optional]string message)
        {
            return message;
        }
    }
    
  2. Объявите свои значения по умолчанию как константы и примените ту же константу в качестве значения по умолчанию.

    class Class2 : Class1
    {
        public override string MethodWithOptParams(string message = DefaultMessage)
        {
            return base.MethodWithOptParams(message);
        }
    }
    
    class Class1
    {
        protected const string DefaultMessage = "default message";
    
        public virtual string MethodWithOptParams(string message = DefaultMessage)
        {
            return message;
        }
    }
    
  3. Используйте null в качестве значения по умолчанию в вашей оболочке и закодируйте два альтернативных вызова базового метода.

    class Class2 : Class1
    {
        public override string MethodWithOptParams(string message = null)
        {
            if (message == null)
            {
                return base.MethodWithOptParams();
            }
            else
            {
                return base.MethodWithOptParams(message);
            }
        }
    }
    
    class Class1
    {
        public virtual string MethodWithOptParams(string message = "default message")
        {
            return message;
        }
    }
    
person Steve    schedule 20.12.2013
comment
как OptionalAttribute изменит поведение C#? Вот это совсем бесполезно. Третий метод такой же, как и в классе inner (только более длинный). Второй вариант хорош, я бы не стал его делать для строк из-за локализации, но он работает. - person Adriano Repetti; 20.12.2013
comment
OptionalAttribute просто позволяет указать необязательный параметр без указания значения по умолчанию. Функционально эквивалентен Type parameter = default(Type). - person Steve; 20.12.2013
comment
Вы совершенно правы, что использование строк по умолчанию часто является плохой идеей из-за локализации, если только это не значение поиска в файле ресурсов. - person Steve; 20.12.2013
comment
Насчет [Необязательно], да вот почему в том примере я считаю бесполезным! Я согласен со вторым комментарием (даже если... честно... мне тоже не нравятся локализованные сообщения об исключениях, потому что большую часть времени это только информация, которую вы получаете от клиентов...) - person Adriano Repetti; 20.12.2013
comment
+1 за метод 2. Метод 1 неприменим в моем случае, так как у меня есть много оберток вокруг одного основного метода, поэтому при использовании мне все равно нужно будет указать значение по умолчанию для каждой оболочки, и, кроме того, основной метод может быть вызван без оболочки, поэтому в любом случае он должен иметь правильное значение по умолчанию. Кроме того, почему вы разделили оболочку на подклассы? В моем вопросе основной метод и обертки - это просто статические методы, и в них нет наследования. - person Suzanne Soy; 20.12.2013