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

четверг, 25 ноября 2010 г.

Немного а Drag'n'Drop в WPF.

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

Вначале всё казалось просто и понятно: есть Drag Source, который по каким-то действиям может инициировать драг-н-дроп, описав те эффекты, с которыми может оперировать Drop Target. Дальше, дроп таргет реагирует на события, такие как DragEnter, DragOver, Drop, применяя те или иные эффекты, после чего уже Drag Source реагирует на совершённые действия (например завершение операции путем дропа или отмены).
Немного сумбурно, поэтому попытаюсь изобразить на картинке:
Схематичное изображение процесса Drag'n'Drop
По сути две части (Drag Source и Drop Target) должны являться независимыми, но просматривая примеры использования я не обнаружил независимой реализации. Практически везде обработка действия Move (чаще всего удаление данных из источника) происходила в реализации метода OnDrop. То есть сам Drop Target определял действия с Drag Source.

Создадим небольшое приложение, пусть главное окно выглядит так:
<Window x:Class="DragDropTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Drag'n'Drop Test" Height="350" Width="525">
  <StackPanel>
      <ListBox x:Name="DragSource" SelectedValuePath="Content"
        GiveFeedback="OnGiveFeedbackRaised"
        QueryContinueDrag="OnQueryContinueDragRaised"
        PreviewMouseMove="DragSource_OnPreviewMouseMove" >
        <ListBoxItem>first</ListBoxItem>
        <ListBoxItem>second</ListBoxItem>
      </ListBox>
    <TextBox x:Name="AddTextBox"></TextBox>
    <Button Click="OnAddPressed">Add</Button>
  </StackPanel>
</Window>
Я не стал сильно мучиться с тем, как именно начинать операцию Drag'n'Drop, поэтому код DragSource_OnPreviewMouseMove выглядит следующим образом (чуть более правильно начало описывается тут):
private void DragSource_OnPreviewMouseMove(object sender, MouseEventArgs e)
{
  if (e.LeftButton == MouseButtonState.Pressed && DragSource.SelectedItem != null)
  {
    var data = new DataObject(DataFormats.Text, DragSource.SelectedValue);
    DragDrop.DoDragDrop(DragSource, data, DragDropEffects.All);
  }
}
Теперь посмотрим, что же мы можем узнавать при помощи фидбека:
  • GiveFeedbackEventArgs позволяют узнать, какой именно эффект сейчас используется и позволяет задать вид курсора.
  • QueryContinueDragEventArgs позволяет узнавать состояние операции, получить состояние клавиш на текущий момент.
Последнее кажется несколько странным, поскольку от состояния клавиш Drag Source не может получить никакой полезной информации, из-за того что эффект определяется поведением Drop Target'а. Начиная с этого места мы имеем первый хак:
private void OnGiveFeedbackRaised(object sender, GiveFeedbackEventArgs e)
{
  _lastEffect = e.Effects;
}
А именно сохранение последнего применённого эффекта, который нам и предстоит обрабатывать по окончанию действия. Следующая бочка дёгтя заглючается в следующем:
Вопреки логике, событие QueryContinueDrag с Action равным DragAction.Drop не приходит. Про это даже заведена бага. Для разрешения проблемы я использовал следующий грязный трюк: можно считать что Drag'n'Drop завершился в тот момент, когда левая кнопка мыши перестала быть зажатой. Мне кажется, что подобное поведение будет правильным для большинства сценариев.
private void OnQueryContinueDragRaised(object sender, QueryContinueDragEventArgs e)
{
  if((e.KeyStates & DragDropKeyStates.LeftMouseButton) == DragDropKeyStates.None)
  {
    OnEndDrag(_lastEffect);
  }
}
И уже на OnEndDrag обрабатывается завершение операции, а именно удаление элемента из списка если выбрано действие Move.
Готовое приложение можно скачать отсюда. Оно, по сути, является независимой реализацией Drag Source и позволяет корректно вставлять текст, например, в Word или Notepad++ (кстати что любопытно они используют разные комбинации клавиш, для создания эффекта Move).

четверг, 14 октября 2010 г.

На злобу дня

Некоторые люди создают совершенно глупые системы, которые якобы не позволяют выносить внутрикорпоративную информацию из офиса. Где-то такие системы могут и работать, но уж точно не в обществе программистов.
Что самое печальное — одна из таких систем внедрена на моей текущей работе. После того, как «Dropbox» продолжил отлично работать вместе с этой системой защиты, относительно её качества у меня не осталось никаких сомнений.
Но кроме своей полной бесполезности система имеет свойство доставлять некоторые неприятности. Например, при выборе файла для отправки через браузер вылезает предупреждение о том, что так поступать нехорошо и приходится убивать процесс. Жутко неудобно. При этом, если вбивать сразу полный путь, то всё работает великолепно. Но заниматься строковой магией вручную противно.
И пока я не разберусь, как же система перехватывает обращения к диалогу выбора файла родилось временное решение: добавить действие копирование полного адреса в контекстное меню файла.

Для этих целей была написана жутко длинная и крайне сложная программа
class Program
{
  [STAThread]
  public static void Main(string[] args)
  {
    if(args != null && args.Any())
    {
      Clipboard.SetText(string.Join(string.Empty,args));
    }
  }
}
А дальше пошли разбирательства с расширениями Windows Shell. Следует сказать, что дела с ним раньше я особо не имел, поэтому делал всё максимально рабоче-крестьянски. По быстрому ознакомившись со статьёй на мсдне в реестре были создан ключ HKEY_CLASSES_ROOT\*\Shell\CopyPath\Command.
В значение HKEY_CLASSES_ROOT\*\Shell\CopyPath лежит красивое название для пункта меню.
В HKEY_CLASSES_ROOT\*\Shell\CopyPath\Command нахожится G:\CopyItemsPath.exe "%1". То есть вначале путь до программы, а потом аргумент, который отвечает за путь до выбранного файла.

После этих действий я получил готовый пункт меню для каждого файла. Теперь можно придумать занятие поинтересней, например найти как можно отключить блокирование диалогов или шифрование флешки в дурацкой системе защиты от распространения внутрикорпоративной информации.

вторник, 12 октября 2010 г.

«Что есть null» или «Как нам дальше жить»

Мне довольно часто приходится писать бизнес-приложения на объектно ориентированных языках достаточно высокого уровня, например C# или Java. Всё нижесказанное не относится к языкам вроде С++.

За время использования C# у меня успело выработаться довольно строгое отношение к null: встреча с этим значение означает ошибку. Практически всегда.
Если в списке нет элементов, то это должен быть пустой список, а не null.
Если мы вызываем какой-то метод объекта, то null в качестве представителя определённо является ошибкой.
Передача null внутрь метода, который не умеет обрабатывать подобное значение тоже приведёт к ошибке.
От постоянных
if(arg == null)
{
  throw new ArgumentNullException("arg");
}
начинают уставать глаза.
Крайне редко получается, что null являлся одним из значений, при этом не выделяясь среди прочих. Чаще всего, он предполагает какое-то специфические действия.

При этом, ошибки связанные с тем, что что-то не было инициализировано а куда-то пришёл null в качестве недопустимого аргумента периодически возникают. Частично этой проблеме помогают контракты и программист может знать, куда можно передавать null а куда нельзя напрягаясь немного меньше.
Но ведь мы, программисты, люди ленивые и напрягаться не хотим вообще, не так ли? Может быть просто стоит запретить null как значение? Это уже сделано например в Haskell.

Но как же быть тогда с методами, которые предполагают использование null? Нельзя же просто от них отказаться?
Для решения этой проблемы умные люди уже давно завели специальный тип. Можно его описать примерно вот так
public class Maybe<T>:IEquatable<Maybe<T>>
{
  /// <summary>
  ///   Default empty instance.
  /// </summary>
  public static readonly Maybe<T> Empty;
  
  /// <summary>
  ///   Instance constructor.
  /// </summary>
  public Maybe(T value);
  
  /// <summary>
  ///   Gets the underlying value, if it is available
  /// </summary>
  public T Value { get; }
  
  /// <summary>
  ///   Gets a value indicating whether this instance has value.
  /// </summary>
  public bool HasValue { get; }
}
Это очень похоже на стандартную Nullable обёртку, для типов передающихся по значению. На самом деле Empty и есть null, разница лишь в том, что он строго типизирован и совершать недопустимые операции с ним немногим сложнее.
На первых взгляд подобное введение сродни обмену шила на мыло, но всё-таки мы получили одно важное преимущество:
Появятся целые участки кода, в которых Maybe не будет участвовать. Цепочки методов, которые не принимают null и не возвращают его. Теперь никто не передаст туда это значение по ошибке, не вызовет метод с недопустимым параметром или не вернёт null из-за желания поставить на будущее заглушку.
Код сам по себе стал чуть более правильным. Возможно, даже чуточку быстрее (не придётся проводить дополнительные проверки, поскольку отсутствие null будет гарантированно системой типов). К сожалению, подобные конструкции продолжают плодить кучу ветвлений, которые затрудняют читабельность. Давайте попытаемся повысить удобство.

От ветвлений никуда не уйдёшь, если необходимы две ветви вычисления, но ситуация меняется кардинальным образом если есть только одна ветвь. Расширим определение класса Maybe методом
public IEnumerable<T> AsEnumerable()
{
  if (HasValue)
  {
    yield return Value;
  }
}
Это даёт возможность работать с элементов Maybe, как с последовательностью. При этом значению Empty будет соответствовать пустая последовательность, а значимому выражению последовательность из одного элемента.

Maybe<int> delay = GetDelayForOperation(/*  */);
delay.AsEnumerable().ForEach(x => Thread.Sleep(x));
Кстати, а почему-бы не сделать и сам Maybe наследником IEnumerable? Это немного укротит запись, сделает её более читабельной. Кстати, умные люди тоже додумались до такого способа, хоть и предложили решить его несколько иначе.
Но, лично мне, не нравится написание огромного количества лямбд. Кроме того, это не очень удобно, при использовании нескольких аргументов.
Maybe<int> delay1 = GetDelayForOperation(/*  */);
Maybe<int> delay2 = GetDelayForOperation(/*  */);
delay.ForEach(x => delay2.ForEach(y => Thread.Slep(Math.Max(x,y))))
Согласитесь, понять с первого раза что делает эта конструкция довольно сложно. Хотя, я думаю, кто-то уже увидел тут приевшиеся цепочки вычислений и функцию bind.
К счатью, в C# есть так называем язык запросов, который позволяет писать SQL-подобные выражения. Для того, чтобы ипользовать эту возможность понадобитья ещё один класс и немого тёмной магии
public static class Maybe
{
  public static Maybe<V> SelectMany<T, U, V>(this Maybe<T> source, Func<T, Maybe<U>> k, Func<T, U, V> s)
  {
    if (!source.HasValue)
      return Maybe<V>.Empty;
    var u = k(source.Value);
    if (!u.HasValue)
      return Maybe<V>.Empty;
    return s(source.Value, u.Value).ToMaybe();
  }
}
Подобная конструкция позволит записывать жутко страшные выражения весьма компактно. Например вместо
result = null;
var d = GetDirectory();
if(d != null)
{
  var f = GetFile(d);
  if(f != null)
  {
    var lines = GetFileLines(f,d);
    if(lines != null)
    {
      result = lines.Count();
    }
  }
}
можно писать
var result = from d in GetDirectory()
       from f in GetFile(d)
       from lines in GetFileLines(f,d)
       select lines.Count();
И главное, никаких лямбд. На самом деле они просто скрыты за этой языковой конструкцией, но для восприятия это не имеет значения.

Бочкой дёгтя в этой ложке мёда можно считать то, что в настоящее время нет средств чтобы запретить использование null в языке вроде c#, поэтому отказ от null и использование Maybe может быть только на уровне негласной договорённости или правила для StyleCop.
На данный момент я пытаюсь сделать совсем маленький проект, следуя подобной договорённости. Ощущения пока положительные, но для чего-то более масштабного, как мне кажется, необходима поддержка на уровне языка.

Есть ещё несколько маленьких удобств, которые облегчают кодирование и отладку, но они не стоят того, чтобы о ни упоминать. Если хотите, посмотрите более подробно в исходнике.

четверг, 7 января 2010 г.

Каррирование в C#

Под термином каррирование понимается преобразование функции, которое функцию от двух переменных переводит в функцию от одной переменной.
Предположим, что у нас была функция:
   f:  AxB  =>   C
   f: (a,b) -> a + b

Сложение взято для примера. Мы можем её преобразовать следующим образом:
   Curry(f): A => (B =>  C  )
   Curry(f): a -> (b ->a + c)

То есть теперь каждому числу Curry(f) сопоставляет функцию одной переменной. При этом выполняется равенство
   f(a,b) = Curry(f)(a)(b)
Кроме того, при помощи функции каррирования мы можем фиксировать значение первого аргумента функции f.
   AddOne   = Curry( + )(1)
   PowerOf2 = Curry( ^ )(2)

Теперь можно использовать новополученные функции. Например:
   AddOne(5)   = 6
   PowerOf2(3) = 8

Подобные функции дают некоторую гибкость, и могут быть использованы, например при использовании методов Linq.
К сожалению, в третьей версии c# нельзя использовать вот такие конструкции:
internal static class Program
{
   public static int AddFunction(int a, int b)
   {
      return a + b;
   }

   private static void Main()
   {
      var addTwo = FunctionalTools.Curry(AddFunction)(2)
      var addTwoExtention = AddFunction.Curry()(2)
   }
}
Поскольку AddFunction на самом деле является "Method Group", и особенно в случае если Curry перегружен, не может правильно определить сигнатуру метода. Поэтому придётся использовать ручное приведение типов. Вот так:
internal static class Program
{
  public static int AddFunction(int a, int b)
  {
    return a + b;
  }

  private static void Main()
  {
    var addTwo = FunctionalTools.Curry((Func<int, int, int>)AddFunction)(2)
    var addTwoExtention = ((Func<int, int, int>)AddFunction).Curry()(2)    
  }
}
Практически всё уже сказано, осталось только реализовать функционал. К сожалению, сделать самый общий тип мы не сможем, но мы можем сделать покрытие значительной части
функционала. Скажи, вы часто используете методы, которые содержат более, чем 8 параметров? Думаю, довольно редко. Поэтому вначале обьявим несколько делегатов.
public delegate TResult Func<T1, T2, T3, T4, T5, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);
public delegate TResult Func<T1, T2, T3, T4, T5, T6, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
public delegate TResult Func<T1, T2, T3, T4, T5, T6, T7, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
public delegate TResult Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8);

Но обьявлять таким образом кучу делегатов скучно, долго, неинтересно и не информативно. Более того, если попытки написать функцию Curry для каждого возможного варианта не вызывают никакой радости.
Поэтому рекомендую посмотреть в сторону Text Template Transformation Toolkit, про него на хабре уже писали.

Немножечко перепишем объявление делегатов:
<# for(var index = Shapr3FuncCount; index < MaxParametersCount; index++) { #>
    public delegate TResult Func<<#= BuildTemplate(1,index) #>, TResult>(<#= BuildTemplate(1,index,x=>TypeNamePrefix+x+ArgPrefix+x) #>);
<#} #>
При этом были использованы следующие константы и функции:

   private const int Shapr3FuncCount = 5;
   private const int MaxParametersCount = 9;
   private const string TypeNamePrefix = "T";
   private const string ArgPrefix = " arg";
   private string BuildTemplate(int a,int b,Func<int,string> func){
      return string.Join(", ",System.Linq.Enumerable.Range(a, b).Select(func).ToArray());
   }
   private string BuildTemplate(int a,int b){
      return BuildTemplate(a, b, x => TypeNamePrefix + x);
   }
   private string BuildArgs(int howMany){
      return BuildTemplate(0,howMany,x => ((char)('b'+x)).ToString());
   }
Вот мы сгенерировали недостающие делегаты, теперь можно смело приступить непосредственно к написанию функций каррирования.
Всё действие будет происходить внутри конструкции:
<# for(var index = MinParametersCount; index < MaxParametersCount; index++) { #>
Собственно, ничего сложного в написании функции нет. Вот она:
public static Func<T0, Func<<#= BuildTemplate(1,index-1) #>>> Curry<<#= BuildTemplate(0,index) #>>(this Func<<#= BuildTemplate(0,index) #>> func)
{
   return a => (<#= BuildArgs(index - 2) #>) => func(a<#= index>MinParametersCount?", ":"" #><#= BuildArgs(index - 2) #>);
}

А к ней я решил добавить ещё одну функцию, которая позволит сразу зафиксировать первый аргумент какой-либо функции. Мне кажется, что это является одной из наиболее востребованных возможностей каррирования.
public static Func<<#= BuildTemplate(1,index-1) #>> Bind<<#= BuildTemplate(0,index) #>>(this Func<<#= BuildTemplate(0,index) #>> func, T0 value)
{
   return Curry(func)(value);
}

Теперь осталось только сохранится и посмотреть получивший файл. Изменяя константы можно плодить дополнительные функции, которые удовлетворит любые потребности в аргументах, хотя и сомневаюсь, что такие могут реально возникнуть.
Для удобства можно реализовать ещё несколько функций высших порядков. Я приведу лишь одну, которая позволит менять местами аргументы функции:
public static Func Flip<T1,T2,TResult>(this Func<T1,T2,TResult> func)
{
   return (x,y) => func(y,x);
}

За этим рассказ окончен, хочу лишь ещё раз обратить внимание что различные функциональные практики постепенно входят в жизнь обыкновенного программиста, что немного радует.
Шаблон и получившийся в результате кодогенерации файл можно скачать.

среда, 30 декабря 2009 г.

Комбинатор неподвижной точки

Когда мне впервые задали вопрос о том может ли существовать функция вида Func<Func<T,T>,T> без использования конструкций вида default(T) он поверг меня в глубокий когнитивный диссонанс.
Как может существовать функция у которой неоткуда взять значения? Об очевидном варианте
T Fix<T>(Func<T,T> func){
   return func(Fix(func));
}
я не мог даже подумать. Разве возможно делать такие функции? Она будет вызываться бесконечно и не даст результата. В языках типа C# такая конструкция и правда вызовет зацикливание, но вполне может работать в языках вроде питона или хаскеля. Сейчас будет немного кода на Haskell, надеюсь синтаксис будет более-менее понятен всем.
Самый простой пример:
fix f = f( fix f)
const42 x = 42
print(fix const42) -- Угадайте, что выведет эта конструкция?
Если разложить вызов, то мы увидим, чтo имеет место быть следующая цепочка вычислений:
fix const42 -> const42 ( fix const42) -> 42
Последний переход произойдёт из-за того, что нам не нужен аргумент функции, чтобы вычислить её значение.
Возникает вопрос: если же функция будет зависеть от своего аргумента то вычисление не остановится, то как нам быть?
Ну не остановиться, и ладно. Если значение не будет использоваться, то оно и не должно быть вычислено, за что спасибо ленивости Haskell.
: - это функция добавления элемента в голову списка. Например: 1:[2,3] = [1,2,3], n:[] = [n]

Рассмотрим функцию fix (1:). Она возвращает список, который, очевидно, будет бесконечным, но тем не менее его можно будет использовать.
take 3 (fix (1:)) -> take 3 (1:fix (1:)) -> 1:take 2 (fix (1:)) -> 1:(1:take 1 (fix (1:)))
-> 1:(1:(1:take 0 (fix (1:)))) -> 1:(1:(1:[])) -> 1:(1:[1]) -> 1:[1,1] -> [1,1,1]
Вот так просто мы получили результат от функции, которая использует свой параметр. Мы даже создали полезную вещь:
repeat n = fix (n:) - Порождение бесконечного списка из одного повторяющегося элемента.
Бесконечность это не порок, главное чтобы где-нибудь, всё равно внутри или снаружи, цепочка вычислений оборвалась. Какие конструкции могут прервать цепочку?
До этого момента мы предполагали, что тип функции для fix является значимым типом. Но почему бы нам не начать использовать функцию высшего порядка? Попробуем традиционный пример:
factCore f = \x -> if x == 0 then 1 else x * f (x-1)
Тогда функция fix factCore будет являться обыкновенным факториалом. По сути каждый раз вместо функции f будет подставляться функция factCore, из-за чего всё станет крайне похоже на обыкновенную рекурсию.

Давайте попробуем что-нибудь посложнее. Например создать все последовательности длинны k состоящие из чисел от 1 до n, притом чтобы не было двух рядом стоящих одинаковых чисел. Задача высосана из пальца, но тем не менее.
allDiffCore n f = \k cond -> if k == 1 then map (\x->[x]) $ filter cond [1..n] else concat $ map (\x -> map (x:) (f (k-1) (/=x)) ) (filter cond [1..n])
sequences n k = fix (allDiffCore n) k (\x->True)
Небольшие пояснения:
filter - функция, принимающая два параметра: условия и список. Возвращает список объектов, которые удовлетворяют условию.
/= - обыкновенное "не равно". Такая вот весьма математическая запись.
concat - функция, которая объединяет список списков в один список.
На каждом шаге мы берём несколько подходящих элементов и задаём функцию которая определит, подходит ли нам следующий элемент. При помощи такого шаблона можно генерировать много разнообразных последовательностей. Например, чтобы получить все возрастающие последовательности достаточно лишь поменять часть отвечающую за функцию фильтрации, то есть (/=x) заменить на (>x).

А теперь задача на подумать: как написать функцию, которая разрешит задачу о ферзях на шахматной доске размера n*n?
Как вы уже заметили, использование функции fix (кстати она является частным случаем комбинатора неподвижной точки), позволяет избежать прямой рекурсии в функциях, что может быть полезно, например, в лямбда исчислении, поскольку там нельзя использовать обыкновенную рекурсию.

Бонусом предлагаю некий, сильно урезанный комбинатор неподвижной точки, для c#:
public static Func<T1, T2> Fix(Func<Func<T1, T2>, Func<T1, T2>> f)
{
   // Создание функции необходимо использовать, чтобы дальнейшие вызовы происходили непосредственно
   // во время передачи значения внутрь. Это позволяет избежать зацикливания

   return f(x => Fix(f)(x));
}
И дальше использование:
var fact = Fix<int, int>(self => x =>
   {
      if (x == 0)
         return 1;
      return x * self(x - 1);
   });
var result = fact(5);   // 120

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

суббота, 26 декабря 2009 г.

Не переусердствуй.

Типичная задача: если коллекция IEnumerable<T>. Требуется определить, содержится ли в нём как минимум n элементов.
Уже не раз и не два видел подобное решение:
   var answer = collection.Count() > n;
Вопрос: зачем так делают люди? Даже если я отброшу чисто функциональные претензии о работе с бесконечными спискам, то остаётся проблемы производительности, возможные эффекты.
Неужели так сложно делать:
   var answer = collection.Skip(n).Any();
И все будут счастливы. Быть как можно более ленивым выгодно.