C #, ObjectCollection.OfType() и foreach (T элемент в ObjectCollection), IEnumerable к IEnumerable

#c# #foreach #ienumerable #linq-to-objects #generic-list

#c# #foreach #ienumerable #ссылка на объекты #общий список

Вопрос:

К моему личному стилю кодирования относится Enumerable.OfType<T>() . Я использую это везде, где в этом есть хоть какой-то смысл. Особенно IEnumerable<T> обеспечивает мощную функциональность linqToObject. Я ненавижу «небезопасный для типа» цикл ObjectCollections, такой как примеры ниже. Теперь у меня есть несколько вопросов о циклическом просмотре этих «неродственных коллекций».

Вопрос 1: Если я преобразую это ArrayList в Enumerable<T> , насколько велик дополнительный цикл по сравнению с простыми проверками foreach / if?

 var ark = new ArrayList();
ark.Add(new Human());
ark.Add(new Human());
ark.Add(new Animal());
  

Вместо:

 foreach (object passenger in ark)
{
    if (passanger is Human) { }
    if (passanger is Animal) { }
}
  

Я использую:

 foreach (var human in ark.OfType<Human>())
{
}

foreach (var animal in ark.OfType<Animal>())
{
}
  

Вопрос 2: Какой способ приведения / преобразования будет использоваться во время цикла foreach в другую типизированную переменную? Это языковая особенность или это работает из коробки?

 foreach (Human human in ark) { }
  

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

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

1. Примечание редактора: ужасный английский был исправлен.

Ответ №1:

Ans 1:

В вашем примере — возможно, вы фактически дважды повторяете ПОЛНОЕ перечисляемое.

 // i use
foreach (var human in ark.OfType<Human>())
{
}

foreach (var animal in ark.OfType<Animal>())
{
}
  

Ans 2:

Это вызвало бы исключение InvalidCastException, если в ark есть какие-либо нечеловеческие объекты.

Я бы лично предпочел ark.OfTypes<T>() , на случай, если я знаю, что хочу иметь дело только с Human и Animals , но буду игнорировать Elves . Таким образом, код становится намного чище, и вы имеете дело со строго типизированным объектом в вашем цикле foreach.

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

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

1. Список массивов в общий IEnumerable < T>

2. @ben — теперь, после твоей правки, это имеет смысл. Я обновлю ответ.

Ответ №2:

Вы не найдете никакой разницы, поскольку ArrayList реализует IEnumerable. На самом деле, все, что реализует этот интерфейс, может быть использовано в инструкции foreach .

Приведение объекта вместо настройки var приведет к сборке (поскольку это явное приведение), но если у вас есть animal внутри перечисления, вам в конечном итоге придется иметь дело с исключениями приведения.

 foreach (Animal animal in ark) { } // this will blow up an exception if the object is Human
  

Ответ №3:

Чтобы предотвратить повторение коллекции два раза и сохранить какой-то понятный синтаксис, я бы предложил метод расширения, который позволяет связывать запросы foreach для указанных типов. Я подумал, что это было бы забавно:

     static void Main(string[] args)
    {
        var ugly = new ArrayList();
        ugly.Add("strings are evil");
        ugly.Add("yeah");
        ugly.Add(1);
        ugly.Add(3);
        ugly.Add(1234);
        ugly.WithType<int>(x => Console.WriteLine("im a lousy int! "   x))
            .AndType<string>(x => Console.WriteLine("and im dangerous string: "   x))
            .Execute();
        Console.ReadKey();
    }

    static TypedCollectionView WithType<T>(this IEnumerable x, Action<T> action)
    {
        return new TypedCollectionView(x).AndType(action);
    }

    class TypedCollectionView
    {
        private readonly IEnumerable collection;
        private readonly Dictionary<Type, Action<object>> actions = new Dictionary<Type, Action<object>>();

        public TypedCollectionView(IEnumerable collection)
        {
            this.collection = collection;
        }

        public TypedCollectionView AndType<T>(Action<T> action)
        {
            actions.Add(typeof(T), o => action((T)o));
            return this;
        }

        public void Execute()
        {
            foreach (var item in collection)
            {
                var itemType = item.GetType();
                foreach (var action in actions.Where(kv.Key.IsAssignableFrom(itemType)).Select(kv.Value))
                {
                    action(item);
                }
            }
        }
    }
  

итак .. теперь, как сделать это менее отстойным? 😀

Ответ №4:

Enumerable.OfType пройдутся по всем элементам и выполнят is T проверку за вас. Таким образом, каждый OfType вызов будет выполнять итерацию по всем элементам в коллекции. Это должно быть медленнее. Но другое дело, если это медленнее до такой степени, что вы не должны этого делать в своей программе.

Я бы подумал, есть ли смысл помещать объекты классов, у которых нет одного и того же родителя, в одну коллекцию. Может быть, вы могли бы найти какую-нибудь абстракцию, а затем в foreach привести все объекты к базовому классу и использовать полиморфные вызовы.

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

1. этот пример arraylist — не мой код. дженерики с ковариантом намного современнее. я смогу лучше обращаться с «ObjectCollections» в старых частях .NET Framework.