Использует ли необязательный параметр edge cases для принудительной реализации toString через злоупотребление интерфейсом языка?

#c# #optional-parameters

#c# #необязательно-параметры

Вопрос:

У меня есть интерфейс IKey , в котором я хочу иметь метод, который будет возвращать ключ в виде строки. Мы рассмотрели такой метод, как этот:

 String GetAsString();
  

который вернул бы строковое представление, но хотел бы иметь возможность снова объявлять ToString() в интерфейсе, чтобы заставить разработчиков реализовать его, но это не заставляет их, поскольку у них есть реализация, унаследованная от Object . Это было предложено:

 public interface IKey
{
    string ToString(string dummyParameter=null);
}
  

это приводит к принудительной реализации метода в любом реализующем классе, но из-за способа работы с необязательными параметрами вызывающим абонентам не нужно указывать значение для этого, и вы гарантируете, что любые вызовы ToString() метода для объектов, которые либо приведены в качестве интерфейса IKey , либо реализующего класса, всегда будут вызывать реализацию классаа не Object реализация.

В реализациях мы можем просто игнорировать dummyParameter и возвращать то, что мы хотим, в безопасности, зная, что вызов ToString() всегда будет на самом деле вызывать ToString(null) .

Теперь это кажется мне неправильным, но в то же время в этом есть что-то довольно приятное. Это почти то же самое, что иметь метод GetAsString() , поскольку его можно вызывать только в IKey интерфейсе и производных классах, за исключением того, что он выглядит как более естественный ToString() метод, который мы хотим использовать, и который мы можем принудительно реализовать в дочернем классе.

Сказав, что фиктивный параметр, который не используется, кажется неправильным.

Так это ужасно? Или отлично?

И подходит ли этот вопрос для SO или он должен быть для программистов?

Примеры

 public class Key :IKey 
    { 
        public string ToString(string abc = null) 
        { 
            return "100"; 
        } 
    }

Key key = new Key ();
Trace.WriteLine (key.ToString());
Trace.WriteLine (key.ToString(null));
Trace.WriteLine (key.ToString("ac"));
Trace.WriteLine (((object)key).ToString());
  

вывод:

 100
100
100
Blah.Tests.Key
  

Комментарии:

1. Обязательно ли называть его toString() ? Я не вижу преимущества в том, чтобы называть это так. Все, что попытается вызвать toString(), будет просто использовать реализацию объекта, верно? Я имею в виду, мне кажется, что вы проходите через дополнительный обруч здесь, когда вы могли бы просто назвать его как-то иначе и получить ту же функциональность. Я могу ошибаться. Просто мои мысли.

2. @Akron в том-то и дело, что все, что вызывает toString() на нем, не использует реализацию объекта, оно использует реализацию класса (за исключением случаев, когда экземпляр явно приведен как Object . Я добавлю несколько примеров, чтобы было понятно

3. Эрик Липперт написал об этом конкретном угловом случае и о том, что вам нужно учитывать при использовании необязательных параметров в интерфейсах: blogs.msdn.com/b/ericlippert/archive/2011/05/09 /…

4. @JoshuaRodgers спасибо, на эту серию статей была ссылка в вопросе

5. Я не вижу проблемы с getAsString . В любом случае я, вероятно, создал бы средство получения строкового свойства с именем Key, возвращающее ключ в виде строки.

Ответ №1:

Похоже, вы используете интерфейс, в котором вы должны использовать абстрактный класс. Приведенный ниже класс явно требует, чтобы потомки были реализованы ToString .

 abstract class X
{
    public abstract override string ToString();
}
  

Комментарии:

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

2. Полностью согласен. Я бы дал 2, если бы мог 🙂

3. @Sam: Я думаю, что это может показаться вам «странным» способом использования интерфейса, потому что это не самая подходящая программная конструкция для вашей проблемы. Остин попал в точку, что выявленная вами проблема лучше всего подходит для абстрактного базового класса.

Ответ №2:

С моей точки зрения, такой ToString() метод в пользовательском интерфейсе немного все испортил, потому что пользовательский интерфейс предоставил метод со стандартным и хорошо известным именем ToString() .

Я предпочитаю что-то более простое и очевидное, например:

 string KeyText { get; }
  

ИЛИ метод

 string ConvertKeyToString();
  

Ответ №3:

В другом ответе уже предлагается абстрактный класс, который, я думаю, является лучшим вариантом для этого.

Идея добавления toString с параметром по умолчанию в интерфейс не слишком хорошо работает на практике. Разрешение перегрузки найдет toString без параметров при вызове без параметров (что, я должен сказать, кажется интуитивно понятным). Рассмотрим результат этой программы:

 void Main()
{   
    Console.WriteLine(new Key().ToString());
}

public interface IKey 
{
    string ToString(string dummy = null);
}

class Key : IKey 
{   
    public string ToString(string dummy) 
    {
        return "myspecialKey";
    }
}
  

Это выводит объект.Реализация toString() . Поэтому, если вы ограничены в использовании интерфейса, я бы назвал метод как-то иначе, чем toString() .

Комментарии:

1. вист, у вас есть точка зрения, если API ожидает и обрабатывает iKey везде, тогда мы никогда не будем вызывать объект. Реализация toString(), но любые реализации iKey должны были бы предоставлять реализацию toString()

2. Да, но это также означает, что надежность решения зависит от того, вводится ли ключ как Key или IKey . Это может привести к интересным ошибкам … Я думаю, я бы предпочел зависеть от разработчиков, не забывающих переопределять object . toString() или присвоение имени методу как-то иначе.

3. действительно. Для меня это один из наиболее убедительных аргументов против этой идеи. Я очень предпочитаю подход с новым именем. Спасибо.

Ответ №4:

Переориентация

(IMO) — ToString() уже имеет очень четко определенную цель и значение. Вам может быть удобно перехватить его на основе имени, но, говоря, что это требуется, вы переназначаете то, чего не должны.

Ответ заключается в том, чтобы иметь свой собственный отдельный метод, первоначальную идею. Все остальное означает все .СЕТЕВАЯ документация о ToString() становится «неправильной».

Например, свойство тега является объектом во многих элементах управления пользовательского интерфейса. Возможно, вы хотите «пометить» элементы управления в какой-то галерее элементов управления. То, что имя подходит и тип подходит, не означает, что значение одно и то же, и вы можете ухватиться за него и изменить его назначение.

Именование

Я бы также предложил рассмотреть возможность изменения имени вашего интерфейса; если разработчики на самом деле не являются ключами? У меня создается впечатление, что они просто «привязаны» или имеют какой-то ключ, связанный с ними. В этом случае, IKeyed , IIndexed или что-то может быть лучше. Тогда a string Key { get; } становится более привлекательным. Возможно, проблема здесь в именовании.?

Комментарии:

1. Мы не переназначаем, мы хотим, чтобы ключи возвращали разумное значение для toString , но хотели бы, чтобы любая реализация iKey была вынуждена реализовать что-то разумное. Состояние docs ‘Возвращает строку, представляющую текущий объект.’ это именно то, что мы хотим применить. Что касается имен, интерфейс используется объектами, которые являются ключами для разных типов объектов (EntityKey, EntityTypeKey, NetworkElementKey, NetworkElementTypeKey и т. Д.)

2. Итак, можете ли вы подтвердить, что объекты, реализующие iKey, на самом деле являются самими ключами, а не просто «чем-то с ключом»?

3. да, объекты, реализующие iKey, являются фактическими объектами, которые действуют как ключи. Итак, у an Entity есть Key свойство, которое возвращает an IEntityKey , которое реализует IKey

4. Если сам объект является ключом, не было бы лучше реализовать IComparable , достойный GetHashCode и Equals ? Почему объект, который является an IKey , уже возвращен из другого объекта, где все функции, подобные ключам, затем делегируются еще одному объекту на графике, a string ? Я бы настоятельно рекомендовал, чтобы IKey объект был ключом, а не другим string , зависающим от него. Если есть операции, которые не стоит повторять в классах, вместо этого используйте абстрактный KeyBase класс.

5. @SamHolder: Я думаю, вы позволяете себе вольности в отношении того, что требуется от строки, которая represents the current object . Я понятия не имею, является ли 100 long, int32, float и т. Д. То, что вы мне дали, — это значение текущего объекта, а не представление самого объекта. Есть свойство, и есть значение свойства (я знаю, что это не совсем то, что мы обсуждаем, но достаточно близко). ОГРОМНАЯ разница. Будет очень плохо, если я попрошу одно и получу другое. Точно так же мне не понравится, если я попрошу toString() рассказать мне об объекте, который у меня есть, и вместо этого он даст мне значение.

Ответ №5:

Я бы назвал это злоупотреблением из-за проблемы, на которую вы намекаете прямо здесь:

… и вы гарантируете, что любые вызовы метода toString() для объектов, которые либо приведены в качестве iKey интерфейса, либо реализующего класса, всегда будут вызывать реализацию класса, а не реализацию объекта.

Рассмотрим следующий код:

 IKey someKey = ...;
string keyAsString = someKey.ToString();
object someKeyAsObject = (object)someKey;
string keyAsString2 = someKeyAsObject.ToString();
  

Любой, кто смотрит на этот код, предположил бы, что keyAsString и keyAsString2 то же самое. Однако это будет вызывать разные методы, которые могут иметь разное поведение. Ик!

Ответ №6:

Ужасно, если мне нужно выбрать только одно слово… Запутанный и подозрительный более точно описал бы мою реакцию, если бы я столкнулся с этим в дикой природе.

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

Это, безусловно, было бы сюрпризом. Это исключает использование ожидаемого результата и использование ToString() без особых усилий. Тот факт, что нет никаких указаний на требуемые особые усилия, был бы «неожиданным» битом. Реализация по умолчанию ToString() в конечном итоге используется чаще, чем я ожидал. Я бы не стал запрещать или искажать его использование, если бы у меня не было другого разумного способа решить проблему.

Я не думаю, что это было бы «более естественным», чем хорошо названный метод / свойство, которое еще не является членом object .

Комментарии:

1. двойной. toString() (среди нескольких других примеров) обеспечивает перегрузки . toString() …

2. Верно, однако я ожидал бы, что эти перегрузки будут функционировать одинаково каждый раз, когда я использую их в соответствии с документацией. Я был бы раздражен, если бы они вдруг вели себя по-другому. Если вы утверждаете, что, поскольку BCL обеспечивает перегрузки для чего-то подобного ToString() , предполагается и ожидается, что разработчики перехватят одну из этих перегрузок и полностью изменят ее назначение, я категорически не согласен.

Ответ №7:

Только что наткнулся на это. Как насчет того, чтобы интерфейс наследовал IFormattable?

http://msdn.microsoft.com/en-us/library/system.iformattable.aspx

Я сам этого не делал, поэтому могу ошибаться, но, похоже, это позволяет использовать параметр format для ToString метода для вашего интерфейса. Обратите внимание, что вы по-прежнему не изменяете параметр без параметров ToString , что, я согласен с другими, не должно выполняться в интерфейсе, но вместо этого вы используете ToString(string format, IFormatProvider provider) с распознаваемым форматом ввода, который в основном означает «дайте мне описание этого объекта в контексте, который он реализует, что угодно». Очевидно, что для каждого реализованного класса потребуется кодировать метод, но это только правильно.