25 октября 2009 г.

(Зло)употребление C# 4.0 Dynamic – Бестиповое Лямбда-Исчисление, Нумералы Чёрча, и все-все-все… (ч.1)

http://community.bartdesmet.net/blogs/bart/archive/2009/08/17/mis-using-c-4-0-dynamic-type-free-lambda-calculus-church-numerals-and-more.aspx

http://habrahabr.ru/blogs/net/70225/

Введение


Воскресное утро, время для еще одного эпизода в серии Безумные Воскресения. Еще раз в одной категории с риском разрыва мозгов, но ведь это как раз то, что нам нравится, не так ли? На этот раз мы рассмотрим бестиповое лямбда-исчисление в C#. Но погодите, разве C# не типизированный язык? Действительно. Но значит ли это, что все, что вы делаете на C# должно быть статически типизировано? Не обязательно: типизация присутствует в языке как инструмент, который вы можете либо не трогать, либо применить. В этом топике мы рассмотрим как новое ключевое слово dynamic из C# 4.0 под несколько странным углом...

Иногда типы становятся преградами на пути: может что-то изначально нетипизировано (как XML-документы без схемы, веб-сервисы без контракта WSDL, и др.) либо подразумевается, что это что-то динамически типизировано (как объекты, приходящие из динамических языков вроде Ruby или Python). И наконец пресловутые API, которые не придерживаются своих типов: в какой-то момент у вас статическая типизация, но в конце концов вы приходите к тому, что у вас System.Object везде где только можно (как взаимодействие COM библиотеками MS Office). Для всех этих случаев как раз и полагается dynamic C# 4.0. Классический пример проиллюстрировать эту особенность, рассказать об IronPython-объекте из C#. Сначала определение в Python:

class Calc:
  def Add(self, a, b):
    return a + b
def GetCalc():
  return Calc()


* This source code was highlighted with Source Code Highlighter.

Самое прекрасное в этом Python-калькуляторе, это его способность работать со всем, что поддерживает оператор сложения. Другими словами, мы можем скормить ему два целочисленных объекта или две строки, или даже DateTime и TimeSpan. Чтобы произвести эти вызовы, мы используем ключевое слово dynamic в C#:

var py = Python.CreateEngine();
dynamic script = py.ImportModule("Calc");
dynamic calc = script.GetCalc();
int three = calc.Add(1, 2);


* This source code was highlighted with Source Code Highlighter.

Не обращайте внимания на несколько технических строк для загрузки файла Python; здесь самым важным является факт того, что тип переменной calc описан как dynamic, что значит все операции, вызываемые из нее, будут разрешены во время выполнения:

image

Я не буду вдаваться в подробности того как это работает, может в другой раз (вне тематики безумных воскресных топиков), но в этом суть данной возможности. Вместо этого, мы сделаем кое-что в стиле "не пытайтесь повторить это в домашних условиях": введем бестиповое лямбда-исчисление.

Бестиповое лямбда-исчисление как оно есть



Бестиповое лямбда-исчисление, разработанное Алонзо Чёрчем около 1930, это теория о вычислительных аспектах функций. Она рассматривает функции как правила и определяет две дружественные операции чтобы работать с ними: абстракция и аппликация. В сущности, это все, но тем не менее, теория породила собой огромное пространство для исследований. Чтобы убедить вас в этом, предлагаю вам посмотреть в вашем любимом онлайновом книжном магазине какое количество страниц в этой книге:

image

(прим. переводчика: не так уж чтобы совсем много: 621 см. http://books.google.com.ua/books?id=ZdydJZVue50C&dq=the+lambda+calculus+its+syntax+and+semantics&hl=ru&source=gbs_navlinks_s)

Итак, позвольте мне суммировать основные вещи, цитируя/перефразируя книгу выше. Во-первых, мы должны определить концепцию λ-терма:

image

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

Везде, где есть лямбда-термы, их можно обозначить функциями, также как это делается в C# с помощью лямбда-выражений. Например, второй пример выше является функцией с аргументом "x", которая возвращает "x". Другими словами, это тождественная функция. Однако, она не имеет типа: она может оперировать чем угодно (в частности, другими лямбда-термами). На C# мы можем написать это вот так:

Func I = x => x;

где T означает параметр-обобщение (generic parameter). Очевидно, эта функция может оперировать значениями, но также хорошо она может оперировать функциями: принимая функцию, она вернет точно такую же функцию:

I(5) // produces 5
I(I) // produces I

Фактически, ближайшие три примера выше являются закрытыми термами, в том смысле, что все символы, используемые в их "теле функции" (часть после точки) находятся в "области действия" путем абстракции(й) над ними. Мы называем их комбинаторами:

image

Последний терм в предыдущем примере не закрыт: он возвращает "х", который не был абстрагирован наружу. Это отражает концепцию замыкания, которую мы имеем в C#:

R x = …;
Func f = z => x;

Сейчас мы не будем больше углубляться в это, но достаточно сказать, что такие красивые слова как "замыкание" глубоко укоренились в теоретических основах. Считайте себя счастливчиками, что вам довелось работать с языком, возлежащим на таких солидных теоретических основаниях :-).

Далее, нам нужна концепция свободных переменных. Вкратце, она позволит нам идентифицировать те переменные, которые не введены абстракцией в данный терм. Это определение отличается предельной простотой:

image

Для комбинаторов набор свободных переменных будет пустым. Напротив, для нашего последнего примера (один результат в замыкании) набор будет содержать только один "x". Ничего невообразимого, правда?

И наконец, теперь мы можем определить как аппликация каррируется, на основании замещения:

image

Чтобы избежать пересечений имен (что формально можно предотвратить используя соглашение по именованию переменных, применив FV (свободных переменных) выше), мы будем как можно более аккуратными в именовании переменных. Все мы знаем это из C# и это просто теоретическая основа для зон видимости:

Func I1 = x => x;

и

Func I2 = y => y;

фактически являются "одним и тем же". Опасное дело использовать такие большие слова в контексте целой среды выполнения (CLR) и языка (C#).

Я не утверждаю что I1 и I2 ссылаются на тот же делегат: нет идентификации между делегатами, которые говорят что "x => x" и "y => y" одно и то же. Скорее, я указываю здесь на то, что окружение I1 и I2 будет одним и тем же когда применено к одному и тому же объекту. В лямбда-исчислении это осносится к альфа-преобразованиям.

Пока что игнорируя важную проблему конфликтов имен, рассмотрим поближе вышеприведенное правило "бета-преобразования". Идея проста: "аппликация абстракции с другим термом дает в результате замещение". Это почти то же самое, что и вызов делегатов в C#, но все же с некоторыми отличиями: в мире лямбда-исчислений, замещения есть ни что иное как механическое переписывание термов. В языках вроде C#, код компилируется как есть и вызов делегата не переписывает магическим образом тело делегата на лету. Но еще более важно, в C# мы оказываемся непосредственно заинтересованы в семантике вызова по значению: передвызовом, аргументы должны быть приведены к "значению", которое может быть передано в вызов (через механизм вызова делегатов).

Нетипизированное или универсально типизированное? (Untyped or uni-typed?)



А теперь вопрос сегодняшнего дня: можем ли мы типизировать (в смысле системы типов CLR/C#) все лямбда-термы? Вроде как мы уже делали это с комбинатором I, выражая его как Func используя обобщения (дженерики): передавая значение конкретного типа T, результатом является то же самое значение и точно тот же тип. В самом деле, как насчет выведения таких сигнатур? C# не делает этого, но F# может (чероез выведение типов по Хиндли—Милнеру, как это делается обычно в ML-образных языках):

image

Okay, "I" выведен к типу чтобы быть типом который идет от 'a к 'a, где 'a - это нотация для обобщенного параметра. Как насчет K, комбинатора, принимающего два аргумента и всегда возвращающего первый из них (породитель "постоянного" значения):

image

Похоже, мы все еще неплохо продвигаемся. F# вывел тип, который должен быть "‘a к ‘b к ‘a". Вау, что здесь происходит? В C#, это будет выглядеть как Func<T1, Func<T2, T1>>: функция, которая получает аргумент типа T1 возвращает функцию, которая принимает аргумент типа T2 и возвращает значение типа T1. Врубаетесь? То, что здесь произошло, называется "карринг, когда функция с n аргументов превращается во "вложенные" функции, которые обслуживают по одному аргументу за раз. Это означает, что мы можем написать, например, "K 5", чтобы создать новую функцию, которая кушает оставшийся аргумент "у" (любого типа) и всегда возвращает 5.
Даже для такого (относительно) сложного зверя как S, тип может быть выведен:

image

Впечатляюще? На самом деле не так уж трудно. Теперь смотрите как мы сами выведем тип из тела функции. Прежде всего, мы видим x перед двумя термами: "z" и "(y z)". Это значит "x" является функцией с двумя аргументами и предполагается что она кое-что возвращает. Присвоим имена типов для этих вещей: первый аргумент "z" получает тип 'a и результат от "(y z)" будет тип 'b. Результат мы запишем как 'c. Другими словами, тип "x" выведен как ’a –> ‘b –> ‘c. Далее, нам нужно вывести тип "y", на основании нашей изначальной идентификации типа для "(y z)" как ’b. Мы видим, что "y" - это функция с одним аргументом, "z". Мы уже определили тип "z" как ‘a, так что тип "y" принимает вид ‘a –> ‘b. И наконец, наша функция "S" также принимает "z" в качестве третьего аргумента, который типизирован как ‘a. Теперь если это все объединить с возвращаемым типом "x", то получим сигнатуру, показанную выше.

Ну так, это выглядит так, как будто мы можем типизировать все лямбда термы, верно? К сожалению, нет. Вот доказательство:

image

Здесь я написал функцию W, которая получаемый аргумент x применяет сама к себе. Такой простой лямбда терм и нельзя найти тип для него. Присоединяйтесь ко мне в этом упражнении для мозга: W принимает один аргумент x, скажем, типа 'a. Теперь, x применяется к x. Отсюда кажется, что x - это функция с одним аргументом. Этот аргумент - x, который мы уже типизировали как 'a. Как насчет возвращаемого типа? Назовем его 'b, так что теперь мы пришли к тому, что x должен быть типа ‘a –> ‘b, который не то же самое что ‘a, как у нас было раньше: унификация не удалась.

Вот где разница между бестиповым лямбда-исчислением и типовыми вариантами (как типизированное лямбда-исчисление и обобщения и еще нечто, называемое "System F"). C#, будучи типизированным языком не позволяет нам избавиться от типов вообще, так что у нас нет возможности получить нечто "нетипизированное". Но как насчет "универсально типизированного" (благодаря Robert Harper): заменить все типы одним единственным типом. Можем ли мы так? Ответ: с "dynamic" мы это можем!
dynamic W = new Func(x => x(x));

К сожалению, нам фактически нужен отчасти уродливый вызов конструктора делегата, но заметьте как мы присваиваем тип "dynamic –> dynamic" к “dynamic” слева. Другими словами, мы интерпретируем все (не функциональные значения и функциональные "объекты") как dynamic. Код выше компилируется просто замечательно, но как же работает в теле лямбды x(x)? Что ж, во времени выполнения система будет выяснять что за тип x и будет проверять, что он может быть вызван как унарная функция. Например:

dynamic W = new Func<dynamic, dynamic>(x => x(x));
W(1);


* This source code was highlighted with Source Code Highlighter.


Это явно неудачное решение. Оно соответствует "вызову функции целое 1" с аргументом "целое 1". Ясно, что целое значение не может быть использовано как функция (это хорошо!) так что время выполнения выдаст:

Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Cannot invoke a non-delegate type
at CallSite.Target(Closure , CallSite , Object , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
at UntypedLambda.Program.
b__46(Object x)
at CallSite.Target(Closure , CallSite , Object , Int32 )
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1](CallSite site, T0 arg0, T1 arg1)
at UntypedLambda.Program.Main()


То, как это устроено внутри, находится за рамками данного топика но DLR был прав, когда счел данный вызов бессмысленным.
Как насчет такого?

var I = new Func<dynamic, dynamic>(x => x);
dynamic W = new Func<dynamic, dynamic>(x => x(x));
Console.WriteLine(W(I)(42));


* This source code was highlighted with Source Code Highlighter.


А вот так теперь работает. Передавая I в W, получаем I(I), которое сокращается (используя аппликацию или бета-преобразование) в I. Эта функция используется с аргументом 42, возвращает 42.

Вы можете догадаться что … мы собираемя универсально типизировать все наши программы, используя dynamic в этих Безумных Воскресных
топиках. Разве я когда-либо упоминал Scheme? Если нет, я сделаю это сейчас :-).

От переводчика:
На днях выйдет перевод оставшейся части топика, содержащей такие разделы:
SKI combinators
Church Booleans
Church Numerals
Going nuts - recursive functions

14 октября 2009 г.

LINQ для Уток – Возвращаясь к конструкции foreach с Duck Typing для LINQ

http://community.bartdesmet.net/blogs/bart/archive/2009/08/17/linq-to-ducks-bringing-back-the-duck-typed-foreach-statement-to-linq.aspx
http://habrahabr.ru/blogs/net/68430/

Обещаю, что в этот раз будет короткая статья (относительно). Все вы знаете языковую конструкцию foreach в C#, не так ли? Но подумайте дважды прежде чем сказать как именно работает следующий код:
  1. foreach (int x in src)
  2. {
  3.   // Do something with x.
  4. }
* This source code was highlighted with Source Code Highlighter.

Уже знаете ответ? Позвольте мне разочаровать вас: если у вас только один ответ, то вы ошибаетесь. Нет единственного ответа на поставленный вопрос, поскольку вы должны знать больше о типе переменной src чтобы принять окончательное решение насчет того, как вышеприведенный код работает…

Очевидно, вы, должно быть, скажете, что объект должен реализовывать IEnumerable или IEnumerable<T> и, может быть, вы даже упомянете, что в первом случае компилятор приводит тип за вас когда получает значение "x", вызывая свойство IEnumerator.Current. Другими словами, вы преобразуете код в нечто вроде этого:
  1. var e = src.GetEnumerator();
  2. while (e.MoveNext())
  3. {
  4.   var x = (int)e.Current; // without the cast if src was an IEnumerable<T>
  5.   // Do something with x.
  6. }
* This source code was highlighted with Source Code Highlighter.

Достойная попытка, но не совсем верная. Прежде всего, переменная x объявлена во внешней зоне видимости (что причиняет некоторые неприятности, если говорить о замыканиях, но сейчас у нас совсем другая тема...). Во-вторых, перечислитель может реализовывать IDisposable, и в этом случае конструкция foreach обеспечивает корректное высвобождение а ля “using”:
  1. {
  2.   int x;
  3.  
  4.   using (var e = src.GetEnumerator())
  5.   {
  6.     while (e.MoveNext())
  7.     {
  8.       x = (int)e.Current; // without the cast if src was an IEnumerable<T>
  9.       // Do something with x.
  10.     }
  11.   }
  12. }
* This source code was highlighted with Source Code Highlighter.

Это уже более разумно, но мы пропустили другой тип источника, с которым может работать foreach: это любой объект, до тех пор, пока он предоставляет шаблон перечисления GetEnumerator в тандеме с MoveNext и Current. Вот для примера объект, который просто замечательно работает с конструкцией foreach.
  1. class Source
  2. {
  3.   public SourceEnumerator GetEnumerator()
  4.   {
  5.     return new SourceEnumerator();
  6.   }
  7. }
  8.  
  9. class SourceEnumerator
  10. {
  11.   private Random rand = new Random();
  12.  
  13.   public bool MoveNext()
  14.   {
  15.     return rand.Next(100) != 0;
  16.   }
  17.  
  18.   public int Current
  19.   {
  20.     get
  21.     {
  22.       return rand.Next(100);
  23.     }
  24.   }
  25. }
* This source code was highlighted with Source Code Highlighter.

Как это используется, показано ниже:
  1. foreach (int x in new Source())
  2.   Console.WriteLine(x);
* This source code was highlighted with Source Code Highlighter.

Ok, гибко, не правда ли? В самом деле, можно сказать, что в конструкции foreach утиная типизация: имеет значение не номинальный тип (т.е. когда Source явно объявлен как IEnumerable и SourceEnumerator как IEnumerator), а лишь структура объекта, которая и определяет «совместимость» с конструкцией foreach.

Но кто сказал, что foreach над коллекцией сразу начинает думать о LINQ? Допустим, класс Source используется вот так:
  1. List<int> res = new List<int>();
  2. foreach (int x in new Source())
  3.   if (x % 2 == 0)
  4.     res.Add(x);
* This source code was highlighted with Source Code Highlighter.

Выглядит как прекрасный кандидат для LINQ, особенно, если бы мы начали добавлять все больше и больше логики в наш «запрос». Ничего удивительно в таком заключении, но в реальности, к сожалению, это падает и не компилируется:



Почему? Потому что в LINQ статическая типизация (update: в этом месте автор просит прочитать комментарии к его статье и соглашается с тем, что более точным было бы в данном случае говорить о LINQ to Objects), так что LINQ ожидает, что я сошлюсь на номинальную имплементацию перечислителя: на что-то, что явно определено как IEnumerable, а не на что-то, что «случайно» оказалось похожим на IEnumerable. Вопрос дня: как преобразовать существующий структурный перечислитель в номинальный так, чтобы его можно было использовать с LINQ? Конечно, мы можем написать специальный код для объекта Source, который создаст необходимый итератор из Source:
  1. static void Main()
  2. {
  3.   var res = from x in IterateOver(new Source())
  4.        where x % 2 == 0
  5.        select x;
  6.  
  7.   foreach (var x in res)
  8.     Console.WriteLine(x);
  9. }
  10.  
  11. static IEnumerable<int> IterateOver(Source s)
  12. {
  13.   foreach (int i in s)
  14.     yield return i;
  15. }
* This source code was highlighted with Source Code Highlighter.

Но быть может вы в такой ситуации, когда вокруг целое изобилие таких структурных перечислителей (например, некоторые библиотеки автоматизации Office предоставляют GetEnumerator в типах вроде Range, в то время как тип Range не реализует интерфейс IEnumerable, следовательно, он не подходит для использования с LINQ), так что вы хотите обобщить вышеприведенное решение. По сути нам нужна возможность надстроить над любым объектом итератор с утиной типизацией и это подходящая задача для расщиряющего метода и ключевого слова dynamic из C# 4.0:
  1. static class DuckEnumerable
  2. {
  3.   public static IEnumerable<T> AsDuckEnumerable<T>(this object source)
  4.   {
  5.     dynamic src = source;
  6.  
  7.     var e = src.GetEnumerator();
  8.     try
  9.     {
  10.       while (e.MoveNext())
  11.         yield return e.Current;
  12.     }
  13.     finally
  14.     {
  15.       var d = e as IDisposable;
  16.       if (d != null)
  17.       {
  18.         d.Dispose();
  19.       }
  20.     }
  21.   }
  22. }
* This source code was highlighted with Source Code Highlighter.

Вопрос к читателю: почему мы не можем просто написать цикл foreach над "объектом, который приведен к dynamic"? Подсказка: как тогда вы реализуете перевод конструкции foreach в dynamic-объекте?

Да, вы нагромоздите необходимый список методов на System.Object, так что будьте осторожны с использованием этого или же просто используйте вызов старого плоского метода, чтобы "перевести" структурное в номинальное. Обратите внимание каким легким выглядит динамически типизированный код в C# 4.0. С большим количеством приведений типов это выглядит примерно так:
  1. static class DuckEnumerable
  2. {
  3.   public static IEnumerable<T> AsDuckEnumerable<T>(this object source)
  4.   {
  5.     dynamic src = (dynamic)source;
  6.  
  7.     dynamic e = src.GetEnumerator();
  8.     try
  9.     {
  10.       while ((bool)e.MoveNext())
  11.         yield return (T)e.Current;
  12.     }
  13.     finally
  14.     {
  15.       var d = e as IDisposable;
  16.       if (d != null)
  17.       {
  18.         d.Dispose();
  19.       }
  20.     }
  21.   }
  22. }
* This source code was highlighted with Source Code Highlighter.

И теперь мы можем написать так:
  1. var res = from x in new Source().AsDuckEnumerable<int>()
  2.      where x % 2 == 0
  3.      select x;
  4.  
  5. foreach (var x in res)
  6.   Console.WriteLine(x);
* This source code was highlighted with Source Code Highlighter.

Динамический клей – почему бы нет? Фактически, даже объекты из других языков (как Ruby или Python), которые следуют парадигме утиной типизации теперь работают с LINQ, и для существующих совместимых объектов вызов оператора безвреден (но расточителен). Ох, и обратите внимание, что вы можете также иметь IEnumerable в «динамических» объектах, если вы имеете дело с объектами из динамических языков…

Можете ли вы реализовать метод AsDuckEnumerable в C# 3.0? Конечно, если вы ограничите себя методами основанными на рефлексии (оставлено в качестве упражнения для читателя).

Наслаждайтесь!
_________
Текст подготовлен в ХабраРедакторе

C# 4.0 и вариантность

http://joeseymour.blogspot.com/2009/08/c-40-and-variance.html
http://romaklimenko.habrahabr.ru/blog/66624/

Вариантность всегда была для меня слишком сложной темой, чтобы разобраться в ней. Недавно я делал доклад о новых особенностях C# 4.0 и вариантность была одной из охваченных мной тем. Я хотел бы начать с распространенного сценария, который мне всегда было трудно понять. Возьмем вот такой фрагмент кода:

  1. IList<object> stuff = new List<string>();
* This source code was highlighted with Source Code Highlighter.

Это не будет компилироваться ни в одной из существующих версий .NET Framework. Я всегда пытался понять, почему бы этому не работать. Тип System.String, безусловно, удовлетворяет все требования System.Object. Так почему же это не работает? Обобщение List – это ссылочный тип, что означает что каждый раз когда кто-то сошлется на объект этого типа в коде, он получит указатель на некоторое место в куче. И неважно сколько раз вы используете List, вы всегда получаете ту же самую ссылку т.к. это ссылочный тип. Так вот, давайте разовьем наш предыдущий пример:

  1. IList<object> stuff = new List<string>();
  2. stuff.Add("Joe is awesome");
  3. stuff.Add(9);
  4. stuff.Add(false);
* This source code was highlighted with Source Code Highlighter.


Вот тут и есть проблема! Переменная stuff – это IList<object>. Значит, мы можем добавить в нее любой объект, в том числе int или bool. Но обернитесь на то, что мы только что устроили – .NET Framework хранит ссылку на основное объявление типа, т.е. фактически на List<string>. Мы не можем добавить в список этого типа int или bool, поскольку это не строковые типы. Это кое-что проясняет из того, что меня интересовало так долго. Давайте исследуем теперь такой код:

  1. IEnumerable<object> stuff = new List<string>();
* This source code was highlighted with Source Code Highlighter.


В настоящее время, как выяснено ранее, .NET не позволяет нам такое делать. Подождите, вы считаете, что этот код должен работать? Да, я вынужден согласиться. Такой код должен работать, потому что IEnumerable – это особый тип. Дело в том, что этот тип не принимает модификаций. Единственное, за что IEnumerable отвечает, так это за возврат объектов, т.е. он возвращает объекты (objects coming out). Это сеет некоторые сомнения касательно наших прежних выводов. Вот у нас есть абсолютно корректный сценарий, когда вы должны иметь возможность выполнить присваивание потому что мы не будем делать никаких внутренних модификаций и можем, следовательно, избежать любых таких проблем с типизацией, обнаруженных в предыдущем примере.
Здесь, IEnumerable<string> определенно может выполнить роль IEnumerable<object>, потому что string наследник от object, а значит object - более общий тип. Иначе говоря, тип string ковариантен типу object. Единственная проблема – .NET 3.5 и более ранние версии не имеют способа для разрешения этой ситуации. Переключимся на .NET 4.0. Вот несколько определений интерфейсов:
  1.  
  2. public interface IEnumerable<out T> : IEnumerable
  3. {
  4.   IEnumerator<T> GetEnumerator();
  5. }
  6.  
  7. public interface IEnumerator<out T> : IEnumerator
  8. {
  9.   bool MoveNext();
  10.   T Current { get; }
  11. }
* This source code was highlighted with Source Code Highlighter.


Новое ключевое слово, которое вы видите, “out”, определяет что мы только разрешаем T быть возвращенным из нашего интерфейса. Это разрешит наши предыдущие попытки присвоить IEnumerable<string> к IEnumerable<object> потому что мы просто говорим компилятору, что T никогда не будет передан в наш интерфейс.

Конечно, есть и обратный сценарий, когда тип только передается в ваш интерфейс. Это лучше продемонстрировать посмотрев на следующий класс:

  1. class Program
  2. {
  3.   void Manipulate(object obj)
  4.   { 
  5.   }
  6.   void ContravariantGoodness()
  7.   {
  8.     Action<object> manipulateObject = Manipulate;
  9.     Action<string> manipulateString = Manipulate;
  10.     manipulateString = manipulateObject;
  11.   }
  12. }
* This source code was highlighted with Source Code Highlighter.


Единственное, что вам здесь не видно, это то, что тип Action в C# 4.0 содержит ключевое слово “in” для обобщенного параметра. Как указано ранее, это не будет работать в .NET 3.5 или более ранних версиях. В этом примере тип object контрвариантен типу string потому что object относится к string как более общий класс к более конкретному. Поскольку в этом случае переменная передается как object, мы можем в самом деле передать все что угодно (почти) в .NET, что удовлетворяет этому контракту, включая тип string. Весьма классная примочка.
Нужно помнить главное, что это все основано на ссылочных типах. Нечто типа такого никогда не будет работать в любом из сценариев:

  1. public struct Parent<T>
  2. {
  3.   public T GetT()
  4.   { 
  5.     ...
  6.   }
  7. }
* This source code was highlighted with Source Code Highlighter.


Я очень раз видеть это в .NET 4.0 и я лично считаю, что это лучшее из добавленных новых функций. Теперь идите и проверьте это.
_________
Текст подготовлен в ХабраРедакторе

15 июля 2009 г.

Организация коллекции изображений на основании данных EXIF. Пример на C#

Когда на жестком диске лежит около 10 ГБ отснятых разными аппаратами цифровых фотографий, разложить все это по полочкам не так уж просто.
Конечно, наверняка существуют платные и бесплатные приложения, помогающие разложить фотографии по папкам на основании данных о дате съемки, но для моих требований оказалось проще написать небольшую консольную программу на C#, почти скрипт.
Исходил из следующего:
1. Все фотографии разложены в папки по годам и месяцам (попытки навести порядок предпринимались неоднократно). Необходимо чтобы структура папок была сохранена.
2. Фотографии в папках некорректно отсортированы, т.к. снимал двумя телефонами и двумя фотоаппаратами, у каждого свой формат именования файла. Необходимо переименовать файлы так, чтобы сортировка по имени была сразу же сортировкой по дате, камере. Для надежности, сохранить старое имя файла.
Таким образом, нужно чтобы, файл, например, D:\Pictures\2008-03\11032008.jpg был скопирован в D:\Pictures New\2008-03\20080311 08.26.37 Nokia 5800 Xpres 11032008.jpg
А старую папку пожму архиватором в архив.
Оказалось, что в .NET 3.5 есть удобные классы для чтения метаданных из файла.
В итоге, за небольшое время получается такой код:

  1. using System;
  2. using System.Linq;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Windows.Media.Imaging;
  6.  
  7. namespace Exifer
  8. {
  9.  class Program
  10. {
  11.   static void Main(string[] args)
  12.   {
  13.    var sourceDirectoryPath = string.Empty;
  14.    var targetDirectoryPath = string.Empty;
  15.    var mask = string.Empty;
  16.  
  17.    if (args.Length == 3)
  18.    {
  19.     sourceDirectoryPath = args[0];
  20.     targetDirectoryPath = args[1];
  21.     mask = args[2];
  22.    }
  23.  
  24.    while (!Directory.Exists(sourceDirectoryPath))
  25.    {
  26.     Console.WriteLine("Source path:");
  27.     sourceDirectoryPath = Console.ReadLine();
  28.    }
  29.  
  30.    while (!Directory.Exists(targetDirectoryPath))
  31.    {
  32.     Console.WriteLine("Target path:");
  33.     targetDirectoryPath = Console.ReadLine();
  34.     if (!string.IsNullOrEmpty(targetDirectoryPath))
  35.      Directory.CreateDirectory(targetDirectoryPath);
  36.    }
  37.  
  38.    while (string.IsNullOrEmpty(mask))
  39.    {
  40.     Console.WriteLine("Mask:");
  41.     mask = Console.ReadLine();
  42.    }
  43.   
  44.    var pathes = GetFilesRecursive(sourceDirectoryPath, mask);
  45.  
  46.    foreach (var oldFilePath in pathes)
  47.    {
  48.     var fileStream = new FileStream(
  49.      oldFilePath,  // path
  50.      FileMode.Open,  // mode
  51.      FileAccess.Read, // access
  52.      FileShare.Read); // share
  53.  
  54.     var jpegBitmapDecoder = new JpegBitmapDecoder(
  55.      fileStream,         // bitmapStream
  56.      BitmapCreateOptions.PreservePixelFormat, // createOptions
  57.      BitmapCacheOption.Default);     // casheOption
  58.  
  59.     var bitmapFrame = jpegBitmapDecoder.Frames.FirstOrDefault();
  60.  
  61.     var newFilePath = string.Empty;
  62.  
  63.     if (bitmapFrame != null)
  64.     {
  65.      var bitmapMetadata = jpegBitmapDecoder.Frames[0].Metadata as BitmapMetadata;
  66.  
  67.      if (bitmapMetadata != null)
  68.      {
  69.       if (!string.IsNullOrEmpty(bitmapMetadata.DateTaken))
  70.        newFilePath += Convert.ToDateTime(bitmapMetadata.DateTaken)
  71.         .ToString("yyyyMMdd HH.mm.ss");
  72.  
  73.       if (!string.IsNullOrEmpty(bitmapMetadata.CameraManufacturer))
  74.        newFilePath += " " + bitmapMetadata.CameraManufacturer;
  75.  
  76.       if (!string.IsNullOrEmpty(bitmapMetadata.CameraModel))
  77.        newFilePath += " " + bitmapMetadata.CameraModel;
  78.      }
  79.     
  80.      newFilePath += " " + Path.GetFileNameWithoutExtension(oldFilePath);
  81.     }
  82.  
  83.     newFilePath = oldFilePath
  84.      .Replace(sourceDirectoryPath, targetDirectoryPath)
  85.      .Replace(Path.GetFileName(oldFilePath), newFilePath + Path.GetExtension(oldFilePath));
  86.  
  87.     if (!Directory.Exists(newFilePath))
  88.      Directory.CreateDirectory(Path.GetDirectoryName(newFilePath));
  89.  
  90.     File.Copy(
  91.      oldFilePath, // sourceFileName
  92.      newFilePath, // destFileName
  93.      false);   // overwrite
  94.  
  95.     Console.WriteLine("{0} -> {1}", Path.GetFileName(oldFilePath), Path.GetFileName(newFilePath));
  96.    }
  97.    Console.ReadKey();
  98.   }
  99.  
  100.   public static List<string> GetFilesRecursive(string path, string searchPattern)
  101.   {
  102.    var result = new List<string>();
  103.    var stack = new Stack<string>();
  104.  
  105.    stack.Push(path);
  106.  
  107.    while (stack.Count > 0)
  108.    {
  109.     var directory = stack.Pop();
  110.     result.AddRange(Directory.GetFiles(directory, searchPattern));
  111.     foreach (var subdirectory in Directory.GetDirectories(directory)) stack.Push(subdirectory);
  112.    }
  113.    return result;
  114.   }
  115. }
  116. }
* This source code was highlighted with Source Code Highlighter.

Все 10 гигов обработало успешно.
Правда, в них были только файлы с расширением *.jpg, на других не проверял.

3 июня 2009 г.

Как работает отложенное выполнение (deferred execution) в LINQ

Запись опубликована на romaklimenko 03.06.2009 в 18:30:02

Как работает отложенное выполнение (deferred execution) в LINQ

Как известно, LINQ поддерживает отложенное выполнение запросов. Пример этому можно посмотреть на 101 LINQ Samples в MSDN.

На днях я решил восполнить свой пробел в знаниях и разобраться, как именно это реализовано. Мне было интересно узнать, что возможности для отложенного выполнения запросов были уже в .NET 2.0, так что по сути, LINQ методы вроде Where() можно успешно реализовать в .NET 2.0, разве что в более громоздкой форме из-за отсутствия расширяющих методов.

Но обо всем по порядку и для начала небольшой эксперимент. Ниже приведен код .NET 2.0-приложения:

  1.   class Program
  2.   {
  3.     private static int i;
  4.  
  5.     static void Main(string[] args)
  6.     {
  7.       i = 0;
  8.  
  9.       IEnumerable<int> ints = GetInts();
  10.  
  11.       i = -10;
  12.  
  13.       foreach (var item in ints) Console.Write("{0} ", item);
  14.  
  15.       Console.WriteLine();
  16.       Console.ReadKey();
  17.     }
  18.  
  19.     static IEnumerable<int> GetInts()
  20.     {
  21.       List<int> result = new List<int>();
  22.       for (int j = 0; j < 10; j++) result.Add(++i);
  23.       return result;
  24.     }
  25.   }
* This source code was highlighted with Source Code Highlighter.

Результат: 1 2 3 4 5 6 7 8 9 10

То есть, я изменил i, но ints остались прежними.

Теперь перепишем GetInts() следующим образом:

  1.     static IEnumerable<int> GetInts()
  2.     {
  3.       for (int j = 0; j < 10; j++) yield return ++i;
  4.     }
* This source code was highlighted with Source Code Highlighter.

Результат: -9 -8 -7 -6 -5 -4 -3 -2 -1

Новое deferred query execution в LINQ оказалось хорошо забытой старой особенностью итераторов и IEnumerable<T>.

Объяснение как это работает можно прочесть в C# Language Specification:

10.14.4. Enumerator objects

When a function member returning an enumerator interface type is implemented using an iterator block, invoking the function member does not immediately execute the code in the iterator block. Instead, an enumerator object is created and returned. This object encapsulates the code specified in the iterator block, and execution of the code in the iterator block occurs when the enumerator object’s MoveNext method is invoked.

Компилятор автоматически геренерирует класс, наследующий интерфейсы IEnumerator, IEnumerator<T> и IDisposable. Таким образом, ints – это еще не коллекция элементов, а экземпляр типа, реализующего IEnumerable<int>. Соответственно, из этого типа мы можем получить динамически сгенерированный тип-перечислитель, используя метод GetEnumerator().

Итак, чтобы понятнее проиллюстрировать что же происходит на самом деле, изменю теперь немного метод Main исходного примера:

  1.     static void Main(string[] args)
  2.     {
  3.       i = 0;
  4.  
  5.       IEnumerable<int> ints = GetInts();
  6.  
  7.       IEnumerator<int> enumerator = ints.GetEnumerator();
  8.  
  9.       while (enumerator.MoveNext() && i < 6)
  10.         Console.Write("{0} ", enumerator.Current);
  11.  
  12.       i = -5;
  13.  
  14.       while (enumerator.MoveNext())
  15.         Console.Write("{0} ", enumerator.Current);
  16.  
  17.       Console.WriteLine();
  18.       Console.ReadKey();
  19.     }
* This source code was highlighted with Source Code Highlighter.

Результат: 1 2 3 4 5 -4 -3 -2 -1

Теперь механизм отложенного выполнения очевиден.

Согласно спецификации, объект-перечислитель (или как лучше по-русски назвать enumerator?) может быть в четырех состояниях: before, running, suspended и after. Реализация этих состояний – на совести разработчика. К сожалению, только по сигнатуре метода нельзя определить в каком состоянии нам вернется перечислитель, поэтому если у нас нет исходного кода метода, возвращающего IEnumerable<T>, то придется проверять тестами, либо смотреть в ildasm.

Хаброметр