PDA

Просмотр полной версии : [Статья] Monads [0]


HideFebruary
01.03.2016, 21:25
Если ты, дорогой читатель, еще не знаешь что такое методы расширения - бросай все свои дела и беги изучай! Ведь это невероятно удобная и лакончиная возможность добавить свою логику в чужой(!) класс.

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

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


var str = " ";
if ( String.IsNullOrWhiteSpace ( str ) ) WriteLine ( "Invalide argument" );


Было бы куда логичней, если бы данный метод находился в экземпляре и его вызов происходил следующим образом:


var str = " ";
if ( str.IsNullOrWhiteSpace () ) WriteLine ( "Invalide argument" );


Сразу видно как строка сократилась, этот код более читабельный и лаконичный, без лишних обращений.

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


Методы расширения позволяют "добавлять" методы в существующие типы без создания нового производного типа, перекомпиляции или иного изменения исходного типа. Методы расширения представляют собой особую разновидность статического метода, но вызываются так же, как методы экземпляра в расширенном типе.Для клиентского кода, написанного на языках C# и Visual Basic, нет видимого различия между вызовом метода расширения и вызовом методов, фактически определенных в типе.


Что бы дублировать такой метод расширения в экземпляре класса String, необходимо создать статический класс и в нем определить статический метод со следующей конструкцией:


public static class StringExtension
{
public static bool IsNullOrWhiteSpace ( this String str )
=> String.IsNullOrWhiteSpace ( str );
}


Теперь можно использовать конструкцию такого типа:
var b = " ".IsNullOrWhiteSpace ();

Это решило нашу проблему, мы реализовали локальную инверсию, от чего код стал более удобным. Конечно же это слабый пример в пользу методов расширений, ведь никому хуже не станет если использовать стандартный вариант, тем более он будет работать гораздо быстрей. Но поверь, дорогой друг, это знание тебе еще ни раз пригодится!

Что бы доказать, что предоставленный подход действительно имеет право на жизнь ( а он имеет, ведь подумайте, эту возможность зачем-то ввели сами разработчики ), я покажу более крутой пример!

Помните мою предыдушую статью? ( конечно помните, она же вызвала такой ажиотаж :) ) [Ссылки могут видеть только зарегистрированные и активированные пользователи]

Так вот, сделаем немного другую реализацию данного класса. Добавим метод расширения к перечислению Keys, которым будем проверять нажата ли кнопка.

Создадим статический класс и добавим в него такой статический метод:

public static bool IsPressing ( this Keys key )
=> GetAsyncKeyState ( key ) == PRESSING;

Метод GetAsyncKeyState и константу PRESSING смотри в предыдущей статье, либо в конце темы!

Теперь мы можем проверить легко, нажата ли какая-нибудь из клавиш подобным образом:
var b = Keys.A.IsPressing ();
Либо получить таким образом все нажатые клавиши и отобразить их:
var pressingKeys = Enum.GetValues( typeof( Keys ) ).Cast<Keys>().Where( key => key.IsPressing() ).ToList();
pressingKeys.ForEach ( key => WriteLine ( key.ToString () ) );

Круто ведь, неправда ли?!

Мы можем пойти дальше и вообще миновать перечисление Keys, добавив этот метод расширения еще и к классу String, что бы по названию клавиши определить нажата ли она! Понимаю, что это вакханалия, но ведь весело. :)

Добавим в прошлый статический класс StringExtension такой метод:

public static bool KeyIsPressing( this String str )
{
var names = Enum.GetNames( typeof( Keys ) );
if ( !names.Contains ( str ) ) return false;

var value = (Keys) Enum.Parse( typeof( Keys ), str );
return value.IsPressing ();
}

Данный метод делает следующее:


var names = Enum.GetNames( typeof( Keys ) ); - берет у перечисления имена всех клавиш

if ( !names.Contains ( str ) ) return false; - если такой клавиши как мы указали не существует, то возвращаем ложь. Ведь если такой клавиши нет, то она и не нажата, логично же.

var value = (Keys) Enum.Parse( typeof( Keys ), str ); - получаем значение данной клавиши

return value.IsPressing (); - используем метод расширения у перечисления Keys который написали ранее и он уже вернет результат, нажата ли клавиша которую мы написали.


Использовать это можно так:

if ( "A".KeyIsPressing () ) WriteLine ( "A is pressing!!!" );


Засуньте в цикл и веселитесь. :)

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

В спойлере исходники классов из данной статьи:
public static class StringExtension
{
public static bool IsNullOrWhiteSpace ( this String str )
=> String.IsNullOrWhiteSpace ( str );

public static bool KeyIsPressing( this String str )
{
var names = Enum.GetNames( typeof( Keys ) );
if ( !names.Contains ( str ) ) return false;

var value = (Keys) Enum.Parse( typeof( Keys ), str );
return value.IsPressing ();
}
}

public static class KeysExtension
{
public static bool IsPressing ( this Keys key )
=> GetAsyncKeyState ( key ) == PRESSING;

private const int PRESSING = -32767;

[DllImport ( "User32.dll" )]
private static extern short GetAsyncKeyState ( Keys key );
}

Следующая статья: Monads [1] = Do ([Ссылки могут видеть только зарегистрированные и активированные пользователи])