Pull to refresh

C# 4.0, и несуществующие методы

Reading time6 min
Views3.3K
Original author: Phil Haack
Предупреждение: Нижеследующее многие сочтут извращением с C#. Возможно конечно это и не так, но я Вас предупредил:).

В Ruby есть интересная особенность для перехвата вызовов несуществующих методов. В таких случаях Ruby вызывает метод вашего класса с названием «method_missing». Автор этого текста показывал пример этого на IronRuby в другой статье.

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

Не было до текущего момента!(Барабанная дробь)

В C# 4.0 вводится новое ключевое слово dynamic, которое добавляет динамические свойства к статически типизированному языку. Не бойтесь, никто не заставит вас это использовать(кроме автора этой статьи). На самом деле, оригинальная цель этого — сделать interop с COM намного проще. Но это не тема этой статьи, посмотрим как можно повеселиться с этой фичей:)

Автор решил попытаться реализовать нечто вроде method_missing.

Первое что он написал — это простейший динамический словарь который использует свойства для добавления и получения значений в/из словаря используя имя свойства как ключ. Вот пример использования:

  1.     static void Main(string[] args)
  2.     {
  3.       dynamic dict = new DynamicDictionary();
  4.       dict.Foo = "Some Value"; // Compare to dict["Foo"] = "Some Value";
  5.       dict.Bar = 123; // Compare to dict["Bar"] = 123;
  6.       Console.WriteLine("Foo: {0}, Bar: {1}", dict.Foo, dict.Bar);
  7.       Console.ReadLine();
  8.     }
  9.  
* This source code was highlighted with Source Code Highlighter.


Это не так уж плохо выглядет, и код простой. Чтобы сделать динамический объект, у нас есть выбор или реализовать интерфейс IDynamicMetaObjectProvider или просто наследоваться от DynamicObject. Автор выбрал второй подход потому что это быстрее и проще сделать. Вот код:

  1.  
  2.  
  3.     public class DynamicDictionary : DynamicObject
  4.     {
  5.       Dictionary<string, object>
  6.       _dictionary = new Dictionary<string, object>();
  7.       public override bool TrySetMember(SetMemberBinder binder, object value)
  8.       {
  9.         _dictionary[binder.Name] = value;
  10.         return true;
  11.       }
  12.       public override bool TryGetMember(GetMemberBinder binder,
  13.       out object result)
  14.       {
  15.         return _dictionary.TryGetValue(binder.Name, out result);
  16.       }
  17.     }
  18.  
  19.  
* This source code was highlighted with Source Code Highlighter.


Всё что автор здесь делает — перегружает метод TrySetMember который вызывается при попытке установить свойство динамического объекта.Автор берет имя поля и использует его как ключ словаря. Так же автор мерегружает метод TryGetMember чтобы возвращать значения из словаря.

Стоит отметить, в Ruby на самом деле нету свойств и методов. Всё является методом, надо только позаботиться о method_missing. Не существует field_missing метода, например. В C# есть разница, поэтому есть другой метод который мы можем перегрузить — TryInvokeMember чтобы обрабатывать динамические вызовы.

Какой хаос можно создать с помощью этого в MVC?

Т.к. автор любитель явно типизированного view data в ASP.NET MVC, он любит вставлять некоторые вспомогательные данные в ViewDataDictionary. Конечно это добавляет синтаксический оверхед, который он любит уменьшать. Вот что было:

  1. // store in ViewData
  2. ViewData["Message"] = "Hello World";
  3.  
  4. // pull out of view data
  5. <%= Html.Encode(ViewData["Message"]) %>
* This source code was highlighted with Source Code Highlighter.


Похоже сюда можно впихнуть динамический словарь.

Прежде чем показывать код, сначала покажу результат. Автор создал новое свойство для Controller и ViewPage с названием Data (вместо ViewData), просто чтобы было покороче и изза нежелания называть его VD.

Вот код контроллера:

  1. public ActionResult Index() {
  2.   Data.Message = "<cool>Welcome to ASP.NET MVC!</cool> (encoded)";
  3.   Data.Body = "<strong>This is not encoded</strong>.";
  4.   
  5.   return View();
  6. }
* This source code was highlighted with Source Code Highlighter.


Заметьте что Message и Body на самом деле не свойства Data, это ключи словаря. Эта запись эквивалетна ViewData[«Message»] = "…".

В этом представлении автор создал собственные правила где все доступ к данным будут закодированы html (Html.Encode, хз как это более адекватно перевести) если не использовать знак подчеркивания.

  1. <asp:Content ContentPlaceHolderID="MainContent" runat="server">
  2. <h2><%= Data.Message %></h2>
  3.  <p>
  4.   <%= Data._Body %>
  5.  </p>
  6. </asp:Content>
* This source code was highlighted with Source Code Highlighter.


Имейте ввиду что Data.Message здесь эквивалентна ViewData[«Message»].

Вот скриншот конечного результата:
Скриншот

Вот как автор это сделал. Сначала был создан класс DynamicViewData:

  1. public class DynamicViewData : DynamicObject {
  2.  public DynamicViewData(ViewDataDictionary viewData) {
  3.   _viewData = viewData;
  4.  }
  5.  private ViewDataDictionary _viewData;
  6.  
  7.  public override bool TrySetMember(SetMemberBinder binder, object value) {
  8.   _viewData[binder.Name] = value;
  9.    return true;
  10.  }
  11.  
  12.  public override bool TryGetMember(GetMemberBinder binder,
  13.    out object result) {
  14.   string key = binder.Name;
  15.   bool encoded = true;
  16.   if (key.StartsWith("_")) {
  17.    key = key.Substring(1);
  18.    encoded = false;
  19.   }
  20.   result = _viewData.Eval(key);
  21.    if (encoded) {
  22.     result = System.Web.HttpUtility.HtmlEncode(result.ToString());
  23.    }
  24.    return true;
  25.  }
  26. }
* This source code was highlighted with Source Code Highlighter.


Если приглядеться, вы заметите что автор делает некие махинации в TryGetMember. Это то, где он проверяет на наличие знака подчеркивания перед именем свойства, и в случае наличия — кодирует текст как html (Естественно знак подчеркивания при этом из названия ключа для словаря убирается).

Дальше автор создает собственный DynamicController:

  1. public class DynamicController : Controller {
  2.  public dynamic Data {
  3.   get {
  4.    _viewData = _viewData ?? new DynamicViewData(ViewData);
  5.    return _viewData;
  6.   }
  7.  }
  8.  dynamic _viewData = null;
  9. }
* This source code was highlighted with Source Code Highlighter.


и DynamicViewPage (оба из которых используют новый класс DynamicViewData):

  1. public class DynamicViewPage : ViewPage {
  2.  public dynamic Data {
  3.   get {
  4.    _viewData = _viewData ?? new DynamicViewData(ViewData);
  5.    return _viewData;
  6.   }
  7.  }
  8.  dynamic _viewData = null;
  9. }
* This source code was highlighted with Source Code Highlighter.


В папке Views автор обновил web.config чтобы сделать DynamicViewPage базовым классом по умолчанию для представлений вместо ViewPage. Это можно изменить устанавливая аттрибут pageBaseType элемента .

Автор надеется что это было весело, и прекрасно осознает, что хоть  некоторые и будут ругаться за такое использование кейворда dynamic,  другие могут увидеть потенциал в этой новой фишки.
Tags:
Hubs:
+20
Comments26

Articles