Подтвердить что ты не робот

Интерфейсы, Наследование, Неявные операторы и преобразования типов, почему так?

Я работаю с библиотекой классов под названием DDay ICal. Это оболочка С# для системы iCalendar, реализованная в календарях Outlook, и множество многих других систем. Мой вопрос получен из некоторой работы, которую я делал с этой системой.

Здесь есть 3 объекта

  • IRecurrencePattern - Интерфейс
  • RecurrencePattern - реализация интерфейса IRecurrencePattern
  • DbRecurPatt - пользовательский класс, который имеет оператор неявного типа

IRecurrencePattern: отображается не весь код

public interface IRecurrencePattern
{
    string Data { get; set; }
}

RecurrencePattern: не отображается весь код

public class RecurrencePattern : IRecurrencePattern
{
    public string Data { get; set; }
}

DbRecurPatt: не отображается весь код

public class DbRecurPatt
{
    public string Name { get; set; }
    public string Description { get; set; }

    public static implicit operator RecurrencePattern(DbRecurPatt obj)
    {
        return new RecurrencePattern() { Data = $"{Name} - {Description}" };
    }
}

Сбивая с толку часть: через систему DDay.ICal они используют IList, чтобы содержать коллекцию шаблонов повторения для каждого события в календаре, пользовательский класс используется для извлечения информации из базы данных, а затем ее шаблон повторения через оператор неявного преобразования типов.

Но в коде я заметил, что он продолжал сбой при преобразовании в List<IRecurrencePattern> из List<DbRecurPatt>, я понял, что мне нужно преобразовать в RecurrencePattern, а затем преобразовать в IRecurrencePattern (так как есть другие классы, которые реализуйте IRecurrencePattern по-разному, которые также включены в коллекцию

var unsorted = new List<DbRecurPatt>{ new DbRecurPatt(), new DbRecurPatt() };
var sorted = unsorted.Select(t => (IRecurrencePattern)t);

Приведенный выше код не работает, он выдает ошибку на IRecurrencePattern.

var sorted = unsorted.Select(t => (IRecurrencePattern)(RecurrencePattern)t);

Это работает, поэтому у меня есть вопрос; Почему первый не работает? (И есть ли способ улучшить этот метод?)

Я считаю, что это может быть потому, что неявный оператор находится на объекте RecurrencePattern, а не на интерфейсе, это правильно? (Я новичок в интерфейсах и неявных операторах)

4b9b3361

Ответ 1

В основном вы попросили компилятор сделать это:

  • У меня есть это: DbRecurPatt
  • Я хочу это: IRecurrencePattern
  • Пожалуйста, выясните способ получить от пункта 1. до пункта 2.

У компилятора, хотя у него может быть только один выбор, не позволяет вам это делать. Оператор трансляции конкретно говорит, что DbRecurPatt можно преобразовать в RecurrencePattern, а не в IRecurrencePattern.

Компилятор только проверяет, содержит ли один из двух типов правило о том, как конвертировать из одного в другое, он не позволяет выполнять промежуточные шаги.

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

Итак, следующий вопрос будет следующим: как я могу это сделать? И ответ: вы не можете.

Компилятор не позволяет вам определять пользовательский оператор преобразования на интерфейс или из него. Другой вопрос здесь о переполнении стека имеет более подробную информацию.

Если вы попытаетесь определить такой оператор:

public static implicit operator IRecurrencePattern(DbRecurPatt obj)
{
    return new RecurrencePattern() { Data = $"{obj.Name} - {obj.Description}" };
}

Компилятор скажет следующее:

CS0552
'DbRecurPatt.implicit operator IRecurrencePattern (DbRecurPatt)': пользовательские преобразования на интерфейс или из него не разрешены

Ответ 2

Почему первый не работает?

Потому что вы запрашиваете время выполнения для двух неявных преобразований: от одного до RecurrencePattern и от одного до IRecurrencePattern. Среда выполнения будет искать только прямую неявную связь - она ​​не будет проверять все возможные маршруты, чтобы вы попросили ее уйти. Предположим, что было несколько неявных преобразований для разных типов классов, которые реализуют IRecurrencePattern. Какой из них выберет время исполнения? Вместо этого он заставляет вас указывать отдельные роли.

Это описано в разделе 6.4.3 спецификации языка С#:

Оценка пользовательского преобразования никогда не включает в себя более одного пользовательский или отмененный оператор преобразования. Другими словами, преобразование из типа S в тип T никогда не будет определяемое пользователем преобразование из S в X, а затем выполнение определяемого пользователем преобразование из X в T.

Ответ 3

Как уже указывали другие, вы не можете совершать прямой переход от DbRecurPatt до IRecurrencePattern. Вот почему вы заканчиваете этот очень уродливый двойной бросок:

var sorted = unsorted.Select(t => (IRecurrencePattern)(RecurrencePattern)t);

Но, для полноты, следует упомянуть, что можно перейти от DbRecurPatt к a IRecurrencePattern без каких-либо бросков с вашим текущим дизайном. Просто для этого вам нужно разделить свое выражение на несколько операторов, и при этом код становится значительно уродливее.

Тем не менее, хорошо знать, что вы можете сделать это без каких-либо бросков:

var sorted = unsorted.Select( t => {
    RecurrencePattern recurrencePattern = t; // no cast
    IRecurrencePattern recurrencePatternInterface = recurrencePattern; // no cast here either
    return recurrencePatternInterface;
});

ИЗМЕНИТЬ

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

var sorted = unsorted
    .Select<DbRecurPatt, RecurrencePattern>(t => t) // implicit conversion - no cast
    .Select<RecurrencePattern, IRecurrencePattern>(t => t); // implicit conversion - no cast

Ответ 4

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

var sorted = unsorted
   .Select<DbRecurPatt, RecurrencePattern>(t => t)
   .ToList<IRecurrencePattern>();

Вы также можете объединить этот ответ с sstan, чтобы избежать дополнительной инструкции Linq.

Ответ 5

... и ответить на ваш последний вопрос о неявном операторе - нет, вы не можете определить неявный оператор на интерфейсе. Эта тема более подробно рассматривается в этом вопросе:

неявный оператор, использующий интерфейсы