2009-09-04 2 views
3

В коде существует только один тип, который реализует IResourceConverter. Вот что ищут два следующих выражения linq. Первый не находит. Последнее делает. Однако оба они эквивалентны синтаксисам (или, по крайней мере, должны быть!).linq головоломка ... эквивалентный синтаксис ... не эквивалентные результаты!

Linq Заявление 1:

List<Type> toInstantiate = AppDomain.CurrentDomain.GetAssemblies() 
    .SelectMany(assembly => assembly.GetTypes()) 
    .Where(type => typeof(IResourceConverter).IsAssignableFrom(type) 
     && type != typeof(IResourceConverter)) 
    .ToList(); 

Это возвращает 0 результатов.

Linq Заявление 2:

Я оставил LINQ нетронутой для пункта где, который я разразившийся и сделал эквивалент с петлей Еогеаспа

List<Type> toInstantiate = new List<Type>();    
List<Type> allTypes = AppDomain.CurrentDomain.GetAssemblies() 
    .SelectMany(assembly => assembly.GetTypes()) 
    .ToList(); 

foreach (Type t in allTypes) 
{ 
    if (typeof(IResourceConverter).IsAssignableFrom(t) 
     && t != typeof(IResourceConverter)) 
    toInstantiate.Add(t); 
} 

В этом случае toInstantiate имеет 1 результат за исключением того, ... именно то, что я ожидал.

Любое объяснение этого странного поведения?

+0

Я предполагаю, что 1 результат toInstantiate действительно реализует IResourceConverter? Возможно, вам захочется добавить/уточнить некоторые факты ... в настоящий момент контекст неоднозначен. – jrista

+0

Извините, jrista. Да, существует один тип, который реализует IResourceConverter. Это то, что ищут выражения linq. Первый не находит. Последнее делает. Однако оба они кажутся эквивалентными синтаксисами. – Daniel

+0

Я заметил, что это также происходит, если вы все еще используете linq, но держите его отдельно и запрашиваете его из allTypes. –

ответ

2

Запустите следующую программу и сравните файлы a.txt и b.txt с помощью инструмента для удаления.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Collections; 
using System.IO; 
using System.Reflection; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var x = Foo().OrderBy(t => t.FullName).Select(t => t.FullName); 
      var y = Bar().OrderBy(t => t.FullName).Select(t => t.FullName); 

      File.WriteAllLines("a.txt", x.ToArray()); 
      File.WriteAllLines("b.txt", y.ToArray()); 
      Console.ReadKey(); 
     } 

     private static List<Assembly> Foo() 
     { 
      List<Type> toInstantiate = AppDomain.CurrentDomain 
       .GetAssemblies().SelectMany(assembly => assembly.GetTypes()) 
       .ToList(); 

      return toInstantiate.Select(t => t.Assembly).Distinct().ToList(); 
     } 

     private static List<Assembly> Bar() 
     { 
      List<Type> toInstantiate = new List<Type>(); 
      List<Type> allTypes = AppDomain.CurrentDomain 
       .GetAssemblies().SelectMany(assembly => assembly.GetTypes()) 
       .ToList(); 

      foreach (Type t in allTypes) 
      { 
       toInstantiate.Add(t); 
      } 

      return toInstantiate.Select(t => t.Assembly).Distinct().ToList(); 
     } 
    } 
} 

Я замечаю большую разницу в сборках, которые могут видеть две части кода. А именно, вторая функция, Bar может видеть сборки, основанные на linq, невозможно.

Более интересно, если я отменяю порядок выполнения, теперь Foo может видеть сборки, другие не могут, т. Е. Точное обратное.

Наконец, если я запускаю первый запрос дважды, выход идентичен, так:

 
    Foo, Foo, Bar 
    Foo, Bar, Foo 
    Bar, Bar, Foo 
    Bar, Foo, Bar 

Все тот же результат.

Так что мое единственное предположение - некоторые сборки загружаются одним запросом, который другой запрос не вызывает загрузки.

+0

Очень хороший материал - спасибо за работу над этим Хранителем. – Daniel

0

Я не специалист по LINQ, но я могу рискнуть догадываться; это похоже на проблему, с которой я столкнулся в Hibernate (инструмент Java ORM):

В Hibernate свойство Collection можно инициализировать лениво. Когда свойство не инициализируется, Hibernate использует инструментарий байт-кода для создания прокси-серверов, путем подкласса Parent и добавления собственного поведения для выполнения ленивой операции загрузки. Если у меня есть классы вроде:

class Test { 
Collection<Parent> getEntities() //lazy 
} 

class Parent extends Child { 
} 

class Child { 
} 

Я могу вызвать getEntities(), чтобы вернуть коллекцию родительских объектов. Поскольку мои getEntities отмечены ленивыми, возвращаемые объекты автоматически генерируются подклассами Parent. Даже если один из элементов коллекции представляет дочерний элемент, проверка вроде: «myEntity instanceof Child» не будет работать, потому что фактический объект является только прокси-сервером для Ребенка, а не для реального объекта Child.

Я понимаю, что LINQ - это механизм запросов, который может использоваться для доступа к данным или для объектов. В вашем случае, возможно, объект типа «type» в вашем аргументе where - это какой-то прокси-запрос для запроса к реальному объекту типа, аналогичный описанному выше случаю, так что isAssignable() определяет, что прокси-объект не реализует IResourceConverter?

0

Убедитесь, что ниже выражение действительно имеет значение истинно (то есть вывести его из заявления LINQ, и оценить его непосредственно против самого типа):

bool doesImplIface = typeof(IResourceConverter).IsAssignableFrom(type) && type != typeof(IResourceConverter); 

Единственная причина, где положение будет фильтровать результатом является то, что выражение не оценивает true. Выполнение вышеуказанной строки кода должно четко указывать, выполняет ли выражение, которое вы пытаетесь выполнить, так, как вы думаете, или нет. Если нет, настройте его, пока не получите соответствующее поведение, и положите новое выражение в оператор LINQ.

+0

Дело в том, что оба выражения идентичны, единственное отличие состоит в том, что одно из них происходит из списка, а другое - с лениво оцененным перечислителем. –

+0

Я бы ожидал, что ленивый перечислитель и список будут вести себя одинаково. Не важно, оцениваете ли вы по частям или как составное целое ... результат должен быть одинаковым. По крайней мере, поскольку я понимаю IEnumerable и LINQ, он должен. – jrista

2

Это, как представляется, также дает правильный результат, но я не уверен, почему.

List<Type> allTypes = AppDomain.CurrentDomain 
    .GetAssemblies().SelectMany(assembly => assembly.GetTypes()) 
    .ToList(); 

List<Type> toInstantiate = allTypes 
    .Where(type => typeof(IList).IsAssignableFrom(type) && type != typeof(IList)) 
    .ToList(); 

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

+1

Разница в том, что таким образом вы сначала загружаете все зависимые сборки, а затем проверяете 'IsAssignableFrom'. Когда есть только один запрос LINQ, к моменту проверки единственные загруженные зависимые сборки - это те сборки, которые мы уже выполнили. Под «зависимым» здесь я подразумеваю сборки, содержащие базовые типы/реализованные интерфейсы типов, которые мы итерируем. Тем не менее, я до сих пор не понимаю, почему это должно иметь значение. –

Смежные вопросы