Страницы

Поиск по вопросам

Показаны сообщения с ярлыком ienumerable. Показать все сообщения
Показаны сообщения с ярлыком ienumerable. Показать все сообщения

воскресенье, 9 февраля 2020 г.

В чем разница в массивах IEnumerable<T> и List<T>?

#c_sharp #linq #типы #ienumerable


Возможно вопрос не совсем корректный.
Допустим у нас имеется следующий код:

  List listValues = new List { 1, 2, 3 };
            var t1 = listValues.ToList();


В данном случае переменная t1 будет ссылаться на новый объект List с аналогичными
значениями.

IEnumerable listValues2 = new List { 1, 2, 3 };
        var t2 = listValues2.ToList();


Здесь, как я понял происходит то, что t2 ссылаться на новый объект IEnumerable с
аналогичными значениями (массив t2 содержит 1,2,3).

Разнится в том, что при добавлении элементов во втором случае надо явно привести
к типу List, так? 

 ((List)listValues2).Add(4);


И/или имеются еще различия в данном случае?
    


Ответы

Ответ 1



t2, как и t1, будет ссылаться на новый объект List. IEnumerable - интерфейс, создать экземпляр интерфейса нельзя. listValues2 - интерфейсная ссылка. Она может указывать на любой экземпляр любого класса, реализующего IEnumerable. Например, на экземпляр класса Queue или Stack. IEnumerable необходим для оператора foreach, так как определяет метод GetEnumerator(). В этом интерфейсе не определяется метод Add(). Этот метод определен в интерфейсе ICollection, который также реализуется классом List. Именно поэтому для добавления элементов по ссылке listValues2 приходится предварительно явно приводить типы. При этом, если ссылка listValues2 указывала на экземпляр класса, отличного от List произойдет исключение времени выполнения.

Ответ 2



Здесь, как я понял происходит то, что t2 ссылаться на новый объект IEnumerable с аналогичными значениями Тип времени выполнения в обоих случаях одинаковый - List. Более того, даже если заменить var ... на IEnumerable ..., ничего не изменится, тип времени выполнения останется тем же, так как метод ToList всегда возвращает объект типа List. (Кроме того, объект просто не может иметь тип IEnumerable, так как экземпляр интерфейса создать нельзя.) Чтобы убедиться на практике, выполните код: List listValues = new List { 1, 2, 3 }; var t1 = listValues.ToList(); Console.WriteLine(t1.GetType()); IEnumerable listValues2 = new List { 1, 2, 3 }; var t2 = listValues2.ToList(); Console.WriteLine(t2.GetType()); IEnumerable listValues3 = new List { 1, 2, 3 }; IEnumerable t3 = listValues3.ToList(); Console.WriteLine(t3.GetType()); Он выведет System.Collections.Generic.List`1[System.Int32] System.Collections.Generic.List`1[System.Int32] System.Collections.Generic.List`1[System.Int32] Чтобы все-таки получить тип времени выполнения, отличный от List, можно реализовать метод-итератор: static IEnumerable Foo(List list) { for (int i = 0; i < list.Count; i++) yield return list[i]; } Тогда var t4 = Foo(listValues); Console.WriteLine(t4.GetType()); Выведет имя автоматически сгенерированного компилятором типа-итератора (у меня ConsoleApplication1.Program+d__0)

Ответ 3



Давайте я попробую немножко объяснить по-своему. Зайду с другого конца: зачем на практике могут понадобиться интерфейсы? Допустим, вам понадобилось выводить на консоль содержимое списка List и вы написали такой метод для этого: static void Print(List list) { foreach (var n in list) Console.Write(n + " "); Console.WriteLine(); } Теперь можно его использовать: List list = new List { 1, 2, 3 }; Print(list); Удобно. Но что, если нам понадобилось вывести массив? Пробуем использовать этот же метод: int[] array = new int[] { 4, 5, 6 }; Print(array); Увы, это невозможно. Как быть? Давайте разберёмся, что именно делается со списком в этом методе: он перечисляется (enumerate). В него ничего не добавляется, не изменяется, не удаляется. Всё, что от него требуется - позволить себя перечислять (проходить по нему). Интерфейс IEnumerable как раз и означает, что тип, реализующий его, является перечислым - по нему можно пробежаться в цикле. Изменим наш метод следующим образом: static void Print(IEnumerable list) { foreach (var n in list) Console.Write(n + " "); Console.WriteLine(); } Теперь код Print(array); успешно компилируется и работает. Теперь мы можем передавать в этот метод любой тип, реализующий интерфейс IEnumerable. Конечно, здесь мы ограничены типом int, но оставим это за рамками обсуждения. Более того, другой программист, использующий нашу библиотеку с этим методом, по одной лишь сигнатуре этого метода скажет, что может произойти с коллекцией, которую он подаст на вход: она будет перечислена и только. А если метод принимает List, то внутри метода вполне может произойти добавление новых данных в список или их удаление, или другие нежелательные действия... Таким образом, интерфейсы позволяют использовать не один жёстко заданный тип, а множество разных. А также интерфейсы дают понять, какие действия разрешены. PS: ещё больше гарантий по неизменности данных дают интерфейсы IReadOnly*.

Ответ 4



Ответ немного припоздал, но всё-же. Важно понимать, что тип переменной и тип её значения - не одно и то же. К примеру: using System; using System.Collections.Generic; namespace CSrharpApplicationTest { internal class Program { private static void Main(string[] args) { IEnumerable x = new List(); Console.WriteLine(x.GetType()); } } } Тип переменной - IEnumerable, тип её значения - List. В обоих случаях в Ваших примерах тип t1 и t2 - List, благодаря вызову метода-расширения ToList(), реализация которого выглядит следующим образом: [__DynamicallyInvokable] public static List ToList(this IEnumerable source) { if (source == null) { throw Error.ArgumentNull("source"); } return new List(source); } , где конструктор List описывается так: [__DynamicallyInvokable] public List(IEnumerable collection) { if (collection == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); } ICollection collection2 = collection as ICollection; if (collection2 != null) { int count = collection2.Count; if (count == 0) { _items = _emptyArray; } else { _items = new T[count]; collection2.CopyTo(_items, 0); _size = count; } } else { _size = 0; _items = _emptyArray; foreach (T item in collection) { Add(item); } } } Разнится в том, что при добавлении элементов во втором случае надо явно привести к типу List, так? Да, во втором случае Вы указываете компилятору, что тип listValues2 - любой тип, реализующий IEnumerable. В интерфейсе IEnumerable, нет методов добавления/изменения/удаления элементов, ведь он описывает минимальный функционал для возможности перечисления по коллекции, соответственно компилятор не может дать гарантий, что то, что лежит внутри listValues2 - коллекция, с методом Add. Поэтому требуется явное приведение типа к List.

среда, 5 февраля 2020 г.

IEnumerable<T> и оператор foreach() [дубликат]

#c_sharp #foreach #ienumerable


        
             
                
                    
                        
                            На этот вопрос уже дан ответ здесь:
                            
                        
                    
                
                        
                            Работа цикла foreach, С#
                                
                                    (1 ответ)
                                
                        
                                Закрыт 2 года назад.
            
                    
Я реализовал простенький односвязный список сначало так :
Клас, производный от интерфейса IEnumerable

class MyList : IEnumerable
{
    public Goods head;
    public Goods tail;
    //add,remove и так далее
    public IEnumerator GetEnumerator()
    {
        Goods curr = head;
        while (curr != null)
        {
            yield return curr.Item;
            curr = curr.Next;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}


Затем его переписал его так, что бы он не был производным от интерфейса IEnumerable:

class MyList 
{
    public Goods head;
    public Goods tail;
    //add,remove и так далее
    public IEnumerator GetEnumerator()
    {
        Goods curr = head;
        while (curr != null)
        {
            yield return curr.Item;
            curr = curr.Next;
        }
   }
}


На MDSN написано :


  The foreach statement repeats a group of embedded statements for each element in
an array or an object collection that implements the IEnumerable or IEnumerable interface.


Второй класс не является производным от IEnumerable. Каким образом я тогда могу спокойно
по нему пройтись оператором foreach()?

MyList myList = new MyList();
myList.Add(10);
myList.Add(20);
foreach(var c in myList)
{
 Console.WriteLine(c);
}

    


Ответы

Ответ 1



Открываем документацию и читаем: Оператор foreach выполняет оператор или блок операторов для каждого элемента в экземпляре типа, который реализует интерфейс System.Collections.IEnumerable или System.Collections.Generic.IEnumerable. Оператор foreach не ограничивается этими типами. Он может применяться к экземпляру любого типа, который удовлетворяет следующим условиям: включает открытый метод GetEnumerator без параметров со следующим типом возвращаемого значения: класс, структура или тип интерфейса; тип возвращаемого значения метода GetEnumerator должен содержать открытое свойство Current и открытый метод MoveNext без параметров с типом возвращаемого значения Boolean. Таким образом, достаточно просто наличия подходящего метода GetEnumerator. Спецификация уточняет, что вот такой foreach foreach (V v in x) embedded_statement на этапе компиляции раскрывается в { E e = ((C)(x)).GetEnumerator(); try { while (e.MoveNext()) { V v = (V)(T)e.Current; embedded_statement } } finally { ... // Dispose e } } (C — тип коллекции, E — тип энумератора, V — тип значения элемента коллекции.) [Таким образом, всё происходит на этапе компиляции, рефлексия не применяется.]

понедельник, 30 декабря 2019 г.

C# вопрос по преобразованию

#c_sharp #ienumerable


Есть класс, реализующий интерфейс IEnumerable

class MyClass : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        for(int i = 0; i < 100; i++)
        {
            yield return i;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)this).GetEnumerator(); //всё хорошо
        return (IEnumerable)this.GetEnumerator(); //error;
    }


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


Ответы

Ответ 1



Потому что он думает, что Вы хотите переменную типа IEnumerable преобразовать в IEnumerator. То есть расценивает это как попытку вызвать this.GetEnumerator(), преобразовать реультат в IEnumerable (явным кастом) и вернуть как IEnumerator (неявным кастом) - тип возвращаемого значения, объявленный в декларации метода. Просто: return this.GetEnumerator();

Ответ 2



В первом случае вы приводите к типу IEnumerable свой this: return ((IEnumerable)this).GetEnumerator(); Во втором случае вы приводите к типу IEnumerable результат this.GetEnumenator() return (IEnumerable)this.GetEnumenator(); Поэтому и ошибка. А вообще, вы можете тут написать вот так: return GetEnumerator(); // это тоже не вызовет ошибки

C# вопрос по преобразованию

#c_sharp #ienumerable


Есть класс, реализующий интерфейс IEnumerable

class MyClass : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        for(int i = 0; i < 100; i++)
        {
            yield return i;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)this).GetEnumerator(); //всё хорошо
        return (IEnumerable)this.GetEnumerator(); //error;
    }


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


Ответы

Ответ 1



Потому что он думает, что Вы хотите переменную типа IEnumerable преобразовать в IEnumerator. То есть расценивает это как попытку вызвать this.GetEnumerator(), преобразовать реультат в IEnumerable (явным кастом) и вернуть как IEnumerator (неявным кастом) - тип возвращаемого значения, объявленный в декларации метода. Просто: return this.GetEnumerator();

Ответ 2



В первом случае вы приводите к типу IEnumerable свой this: return ((IEnumerable)this).GetEnumerator(); Во втором случае вы приводите к типу IEnumerable результат this.GetEnumenator() return (IEnumerable)this.GetEnumenator(); Поэтому и ошибка. А вообще, вы можете тут написать вот так: return GetEnumerator(); // это тоже не вызовет ошибки

понедельник, 8 апреля 2019 г.

IEnumerable и оператор foreach() [дубликат]

На данный вопрос уже ответили: Работа цикла foreach, С# 1 ответ Я реализовал простенький односвязный список сначало так : Клас, производный от интерфейса IEnumerable
class MyList : IEnumerable { public Goods head; public Goods tail; //add,remove и так далее public IEnumerator GetEnumerator() { Goods curr = head; while (curr != null) { yield return curr.Item; curr = curr.Next; } }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } }
Затем его переписал его так, что бы он не был производным от интерфейса IEnumerable
class MyList { public Goods head; public Goods tail; //add,remove и так далее public IEnumerator GetEnumerator() { Goods curr = head; while (curr != null) { yield return curr.Item; curr = curr.Next; } } }
На MDSN написано :
The foreach statement repeats a group of embedded statements for each element in an array or an object collection that implements the IEnumerable or IEnumerable interface.
Второй класс не является производным от IEnumerable. Каким образом я тогда могу спокойно по нему пройтись оператором foreach()?
MyList myList = new MyList(); myList.Add(10); myList.Add(20); foreach(var c in myList) { Console.WriteLine(c); }


Ответ

Открываем документацию и читаем:
Оператор foreach выполняет оператор или блок операторов для каждого элемента в экземпляре типа, который реализует интерфейс System.Collections.IEnumerable или System.Collections.Generic.IEnumerable. Оператор foreach не ограничивается этими типами. Он может применяться к экземпляру любого типа, который удовлетворяет следующим условиям: включает открытый метод GetEnumerator без параметров со следующим типом возвращаемого значения: класс, структура или тип интерфейса; тип возвращаемого значения метода GetEnumerator должен содержать открытое свойство Current и открытый метод MoveNext без параметров с типом возвращаемого значения Boolean

Таким образом, достаточно просто наличия подходящего метода GetEnumerator

Спецификация уточняет, что вот такой foreach
foreach (V v in x) embedded_statement
на этапе компиляции раскрывается в
{ E e = ((C)(x)).GetEnumerator(); try { while (e.MoveNext()) { V v = (V)(T)e.Current; embedded_statement } } finally { ... // Dispose e } }
(C — тип коллекции, E — тип энумератора, V — тип значения элемента коллекции.) [Таким образом, всё происходит на этапе компиляции, рефлексия не применяется.]

пятница, 14 декабря 2018 г.

C# вопрос по преобразованию

Есть класс, реализующий интерфейс IEnumerable
class MyClass : IEnumerable { public IEnumerator GetEnumerator() { for(int i = 0; i < 100; i++) { yield return i; } }
IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); //всё хорошо return (IEnumerable)this.GetEnumerator(); //error; }
Почему первый вариант компилятор принимает, а второй подчеркивает, что такая запись недопустима, если, по идеи, оба эти записи являются операциями преобразования?


Ответ

Потому что он думает, что Вы хотите переменную типа IEnumerable преобразовать в IEnumerator. То есть расценивает это как попытку вызвать this.GetEnumerator(), преобразовать реультат в IEnumerable (явным кастом) и вернуть как IEnumerator (неявным кастом) - тип возвращаемого значения, объявленный в декларации метода. Просто:
return this.GetEnumerator();