⬆️ ⬇️

C # 4.0, and nonexistent methods

Warning: The following will be considered a perversion by many with C #. Perhaps of course this is not the case, but I warned you :).



Ruby has an interesting feature for intercepting calls to nonexistent methods. In such cases, Ruby calls a method in your class called “method_missing”. The author of this text showed an example of this on IronRuby in another article.



For dynamic languages ​​this may be the norm, but this does not happen in statically typed languages.

')

It was not until the present moment! (Drum roll)



C # 4.0 introduces a new dynamic keyword, which adds dynamic properties to a statically typed language. Do not be afraid, no one will force you to use it (except the author of this article). In fact, the original purpose of this is to make interop with COM much easier. But this is not the topic of this article, let's see how you can have fun with this feature :)



The author decided to try to implement something like method_missing.



The first thing he wrote was the simplest dynamic dictionary that uses properties to add and get values ​​to / from the dictionary using the property name as a key. Here is an example of use:



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




This is not so bad looking, and the code is simple. To make a dynamic object, we have the choice to either implement the IDynamicMetaObjectProvider interface or simply inherit from DynamicObject. The author chose the second approach because it is faster and easier to do. Here is the code:



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




All that the author does here is overloading the TrySetMember method, which is called when trying to set a property of a dynamic object. The author takes the field name and uses it as a dictionary key. The author also uploads the TryGetMember method to return values ​​from the dictionary.



It is worth noting that in Ruby there are actually no properties and methods. Everything is a method, you just need to take care of method_missing. There is no field_missing method, for example. There is a difference in C #, so there is another method that we can overload - TryInvokeMember to handle dynamic calls.



What kind of chaos can you create with this in MVC?



Because The author is an amateur of clearly typed view data in ASP.NET MVC, he likes to insert some auxiliary data into the ViewDataDictionary. Of course, this adds a syntactic overhead that he likes to shrink. Here is what happened:



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




It looks like you can cram a dynamic dictionary.



Before showing the code, first show the result. The author has created a new property for the Controller and ViewPage called Data (instead of ViewData), just to be shorter and not wanting to call it VD.



Here is the controller code:



  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. return View ();
  5. }
* This source code was highlighted with Source Code Highlighter .




Note that Message and Body are not really Data properties, they are dictionary keys. This entry is equivalent to ViewData ["Message"] = "...".



In this view, the author has created his own rules where all data access will be encoded with html (Html.Encode, xs as it is more appropriate to translate) if you do not use an underscore.



  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 .




Note that Data.Message here is equivalent to ViewData ["Message"].



Here is a screenshot of the final result:

Screenshot



That's how the author did it. First, the DynamicViewData class was created:



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




If you look closely, you will notice that the author does some fraud in TryGetMember. This is where it checks for the presence of an underscore before the name of the property, and if there is, encodes the text as html (Naturally, the underscore is removed from the key name for the dictionary).



Then the author creates his own 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 .




and DynamicViewPage (both of which use the new DynamicViewData class):



  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 .




In the Views folder, the author has updated web.config to make DynamicViewPage the default base class for views instead of ViewPage. This can be changed by setting the element's pageBaseType attribute.



The author hopes that it was fun, and is well aware that even though some will swear for such use of keyword dynamic, others may see the potential in this new chip.

Source: https://habr.com/ru/post/68152/



All Articles