Почему этот интерфейс должен быть явно реализован?

Возвращаясь на С# через несколько лет, я немного ржавый. Пришел через этот (упрощенный) код, и он оставил мою голову царапин.

Почему вы должны явно реализовать свойство IDataItem.Children? Не удовлетворяет ли нормальное положение Children? В конце концов, свойство используется непосредственно для его удовлетворения. Почему это не подразумевается?

public interface IDataItem {

    IEnumerable<string> Children { get; }
}

public class DataItem : IDataItem {

    public Collection<string> Children { get; } = new Collection<string>();

    // Why doesn't 'Children' above implement this automatically?!
    // After all it used directly to satisfy the requirement!
    IEnumerable<string> IDataItem.Children => Children;
}

Согласно источнику С#, здесь определение Collection<T>:

[System.Runtime.InteropServices.ComVisible(false)]
public class Collection<T> :
    System.Collections.Generic.ICollection<T>,
    System.Collections.Generic.IEnumerable<T>, <-- Right Here
    System.Collections.Generic.IList<T>,
    System.Collections.Generic.IReadOnlyCollection<T>,
    System.Collections.Generic.IReadOnlyList<T>,
    System.Collections.IList

Как вы можете видеть, он явно реализует IEnumerable<T> и из моего понимания, если "X" реализует "Y", тогда "X" является "Y", поэтому Collection<String> является IEnumerable<String> поэтому я не знаю, почему это не удовлетворено.

Ответы

Ответ 1

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

Нам не разрешено писать это:

public interface IDataItem {

    void DoStuff(string value);
}

public class DataItem : IDataItem {

    public void DoStuff(object value) { }
}

Ваш пример тот же, за исключением того, что вы запрашиваете типы возвращаемого значения, а не параметры (и, по очевидным причинам, используете сужение, а не расширение конверсии). Тем не менее, применяется тот же принцип. Когда дело доходит до соответствия подписей, типы должны соответствовать точно 3.

Вы можете попросить язык, который позволил бы такие вещи произойти, и такие языки могут существовать. Но дело в том, что это правила С#.


1 Вне некоторой ограниченной поддержки для Co- и Contra-дисперсии с участием дженериков и интерфейсов/делгатов.

2 Некоторые могут спорить о том, является ли подпись правильным словом для использования здесь, поскольку в этом случае типы возврата имеют значение как типы параметров, общая арность и т. Д.; В большинстве других ситуаций, когда кто-то говорит о сигнатурах метода С#, они будут явно игнорировать тип возвращаемого значения, потому что они (явно или неявно), учитывая то, что говорят правила перегрузки, и для перегрузки типы возврата не являются частью подписи.

Тем не менее, я доволен своим использованием слова "подпись" здесь. Подписи не формально определены в спецификации С# и там, где они используются, часто указывать, какие части подписи не должны учитываться при перегрузке.

3 Не говоря уже о проблемах, которые возникли бы, если бы метод " Children возвращал struct которая произошла для реализации IEnumerable<string>. Теперь у вас есть метод, который возвращает значение типа значения и вызывающего (через интерфейс IDataItem который ожидает получить ссылку на объект.

Таким образом, даже не может использоваться метод как есть. Мы должны (в данном случае) иметь скрытые преобразования бокса для реализации интерфейса. Когда эта часть С# была указана, они были, я считаю, стараясь не иметь слишком много "скрытой магии" для кода, который вы могли бы легко написать себе.

Ответ 2

В вашем примере ваше "нормальное" свойство Child фактически не удовлетворяет требованиям интерфейса. Тип отличается. На самом деле не имеет значения, что вы можете бросить его - они разные.

Подобный пример и, возможно, более очевидный, - это если вы реализуете интерфейс с фактическим методом, который возвращает IEnumerable и пробовал метод ICollection из фактического класса. По-прежнему существует ошибка времени компиляции.

Поскольку @Ben Voigt сказал, что преобразование все еще генерирует некоторый код, и если вы хотите его получить - вам нужно добавить его неявно.

Ответ 3

На этот вопрос был дан ответ (вы должны соответствовать типам интерфейсов), и мы можем продемонстрировать проблему с примером. Если это сработало:

public interface IDataItem {

    IEnumerable<string> Children { get; set; }

    void Wipe();
}

public class DataItem : IDataItem {

    public Collection<string> Children { get; } = new Collection<string>(); 

    public void Wipe() {
        Children.ClearItems();  //Property exists on Collection, but not on IEnumerable
    }
}

И тогда мы используем этот код следующим образом:

IDataItem x = new DataItem();

//Should be legal, as Queue implements IEnumerable. Note entirely sure
//how this would work, but this is the system you are asking about.
x.Children = new Queue<string>();

x.Wipe();  // Will fail, as Queue does not have a ClearItems method within

Либо вы имеете в виду свойство только когда-либо быть перечислимым, либо вам нужны свойства класса Collection - соответствующим образом введите ваш интерфейс.

Ответ 4

Любой класс, который реализует интерфейс, должен содержать определение для метода, которое соответствует сигнатуре, которую указывает интерфейс. Интерфейс определяет только подпись. Таким образом, интерфейс в С# похож на абстрактный класс, в котором все методы абстрактны.

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

Вот хорошее чтение о интерфейсах. Интерфейсы в С#

Надеюсь, это поможет вам в дальнейшем понимании.