1 Востаннє редагувалося leofun01 (29.01.2023 19:02:49)

Тема: [Вирішено] foreach цикл, не очікувана поведінка компілятора

Я писав код (багато коду, дописував старий проект), і в кількох місцях допустив помилку: використав не правильний тип даних в foreach; і я довго не помічав цієї помилки, бо був впевнений що якби тип був не правильний, то середовище кинуло би мені помилку на етапі компіляції. Помилки компіляції не було, і я продовжував розробку.

Але сьогодні я рефакторив старий код, і помітив що відбувається щось дивне: цикл foreach проходить по всіх елементах колекції використовуючи тип, якого в колекції не є. Приклад:

IEnumerable<TypeA> collection = new TypeA[] { ... };
foreach(TypeB item in collection) { ... }

Типи TypeA і TypeB не мають прямого звязку (наслідуваня або агрегації).

В процесі виясненя чому компілятор не кидає помилку, шляхом видаленя кусків коду з struct TypeA і з struct TypeB, я виявив що оператор приведеня типів (operator TypeB(TypeA a)) має значний вплив на foreach. І не важливо який він (implicit чи explicit).

Щоб ви могли відтворити проблему, я підготував простий приклад:

using System;
using System.Collections.Generic;

namespace ForEachLoop_Test {
    public struct A {
        public int Value { get; set; }
        public static explicit operator A(int i) { return new A() { Value = i }; }
    }
    public static class Program {
        public static void Main(string[] args) {
            IEnumerable<int> collection = new int[] { 1, 2, 3 };
            foreach(A item in collection)
                Console.Write("{0} ", item.Value);
        }
    }
}

Як розробник я очікую що компілятор кине помилку в рядку з foreach, бо має бути одне з двох: або int item; або IEnumerable<A> collection.
Але він цього не робить, спокійно компілює цей код, і навіть попередженя (warning) не кидає.

Якого х.. ? Я ж спеціально не додавав implicit щоб будь-які приведеня робити тільки явно, без "магії".

Чому така поведінка в C# ? І чи можливо залишити явне (explicit) приведеня типів і отримувати помилки компіляції користуючи foreach з не правильними типами ?

2

Re: [Вирішено] foreach цикл, не очікувана поведінка компілятора

Найподібніше (за вмістом) що я зміг знайти в специфікації C# 6.0 (en) (розділ 12.9.5) це згадку про

ECMA-334 написав:

implicitly typed iteration variable

, і

ECMA-334 написав:

If among all the types Ti for which there is an implicit conversion from X to IEnumerable<i>, there is a unique type T such that T is not dynamic and for all the other Ti there is an implicit conversion from IEnumerable<T> to IEnumerable<Ti>, then the collection type is the interface IEnumerable<T>, the enumerator type is the interface IEnumerator<T>, and the iteration types Ti.

, і о йой

ECMA-334 написав:

foreach statement of the form

foreach (V v in x) «embedded_statement»

is then expanded to:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        while (e.MoveNext()) {
            V v = (V)(T)e.Current;
            «embedded_statement»
        }
    }
    finally {
        ... // Dispose e
    }
}

Тобто вони викликають explicit operator там де його не має бути (в ідеалі).
Який жах.

Дякую користувачу Firefox is dead за підказку.

Подякували: Firefox is dead1

3

Re: [Вирішено] foreach цикл, не очікувана поведінка компілятора

Ну, це хреново погано, доведеться видалити всі operator'и приведеня типів. А щоб користувачі мали можливість перетворювати одні типи в інші (явно), доведеться додати методи, From або To, ще не вирішив.

4

Re: [Вирішено] foreach цикл, не очікувана поведінка компілятора

Так а як компілятор кине Вам помилку?
IEnumerable теж інтове.
То вже робота для тестувальників, нехай свою копійку відпрацьовують ))

5

Re: [Вирішено] foreach цикл, не очікувана поведінка компілятора

Droid 77 написав:

Так а як компілятор кине Вам помилку?
IEnumerable теж інтове.

Та він собі нехай буде int'овий. Проблема не в конвертації з типу IEnumerable<int> в тип IEnumerable<A>, якої там не є. Проблема в конвертації з int в A, яка там є, і я її зовсім не очкував.

6

Re: [Вирішено] foreach цикл, не очікувана поведінка компілятора

Проблеми не буде доки не буде звернення до проблемної секції.
Може таке бути що є проблема в коді, але ні коли не має звернення до помилки. І все буде Ок.
Так зазвичай і відбуваються авіакатастрофи ))