Click here to Skip to main content
11,630,646 members (78,247 online)
Click here to Skip to main content
Articles » Languages » C# » General » Downloads
Add your own
alternative version

Dynamic Binding in C#

, 4 Feb 2011 Public Domain 47.3K 591 45
Illustrates a dynamic binding implementation in C#
DynamicBindingInCSharp.zip
Properties
using System;
using System.IO;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

namespace DynamicBindinginCSharp
{
  class Program
  {
    static void Main(string[] args)
    {
      InstancePropertySample();
      StaticPropertySample();
      ConsoleColorExample();
      ConsoleRedirectionSample();
      LocalBindingExample();
      StaticFieldExample();

      Console.ReadLine();
    }

    class TestClass
    {
      public string Test { get; set; }
      public static object StaticField;
    }

    static void StaticFieldExample()
    {
      Console.WriteLine(TestClass.StaticField);

      using (10.Bind(() => TestClass.StaticField))
      {
        Console.WriteLine(TestClass.StaticField);
      }

      Console.WriteLine(TestClass.StaticField);
    }

    static void LocalBindingExample()
    {
      var i = 0;

      Console.WriteLine(i);

      using (10.Bind(() => i))
      {
        Console.WriteLine(i);
      }

      Console.WriteLine(i);
    }

    static void InstancePropertySample()
    {
      var tc = new TestClass { Test = "Foo" };

      Console.WriteLine(tc.Test);

      using ("Bar".Bind(() => tc.Test))
      {
        Console.WriteLine(tc.Test);

        using ("Baz".Bind(() => tc.Test))
        {
          Console.WriteLine(tc.Test);
        }

        Console.WriteLine(tc.Test);
      }

      Console.WriteLine(tc.Test);
    }

    static void StaticPropertySample()
    {
      Console.WriteLine(Console.ForegroundColor);

      using (ConsoleColor.Red.Bind(() => Console.ForegroundColor))
      {
        Console.WriteLine(Console.ForegroundColor);
      }

      Console.WriteLine(Console.ForegroundColor);
    }

    static void ConsoleRedirectionSample()
    {
      Console.WriteLine("Console");

      using (File.CreateText("warning.txt").AsConsoleOutput())
      {
        Console.WriteLine("Warning");

        using (File.CreateText("error.txt").AsConsoleOutput())
        {
          Console.WriteLine("Error");
        }

        Console.WriteLine("Warning");
      }

      Console.WriteLine("Console");
    }
  }

  public static class SampleExtensions
  {
    /// <summary>
    /// Redirects Console output temporarily within the using construct; can be nested
    /// </summary>
    /// <typeparam name="T">Inferred, must be TextWriter</typeparam>
    /// <param name="writer">the writer to use</param>
    /// <returns>an instance to wrap use in a using construct</returns>
    public static IDisposable AsConsoleOutput<T>(this T writer) where T : TextWriter
    {
      return writer.With(() => Console.Out, Console.SetOut);
    }
  }

  public static class Dynamic
  {
    // Helper method to generate IL for setter the field in a closure/instance/type
    static MethodInfo GenerateFieldSetter(FieldInfo fi)
    {
      var dm = new DynamicMethod("FieldSetter", typeof(void), new Type[] { fi.DeclaringType, fi.FieldType }, fi.DeclaringType);

      var ilg = dm.GetILGenerator();

      if (!fi.IsStatic)
      {
        ilg.Emit(OpCodes.Ldarg_0);
      }

      ilg.Emit(OpCodes.Ldarg_1);

      if (fi.IsStatic)
      {
        ilg.Emit(OpCodes.Stsfld, fi);
      }
      else
      {
        ilg.Emit(OpCodes.Stfld, fi);
      }

      ilg.Emit(OpCodes.Ret);

      return dm;
    }

    // Expression helper to build getter and setter
    static Binder<T> BuildBinder<T>(T newvalue, Expression<Func<T>> locator, bool dispose)
    {
      var loc = locator.Body as MemberExpression;
      if (loc == null)
      {
        throw new ArgumentException("Must be property or field expression");
      }

      var getter = locator.Compile();
      var x = Expression.Parameter(typeof(T), "x");

      var inst = loc.Expression;
      var prop = loc.Member as PropertyInfo;

      Expression body = null;

      if (prop != null && prop.CanRead && prop.CanWrite)
      {
        body = Expression.Call(inst, prop.GetSetMethod(), x);
      }

      var field = loc.Member as FieldInfo;

      if (field != null)
      {
        if (inst == null)
        {
          inst = Expression.Constant(null, field.DeclaringType);
        }

        body = Expression.Call(GenerateFieldSetter(field), inst, x);
      }

      if (body == null)
      {
        throw new ArgumentException("Not supported, not a field or property that can read and write");
      }

      var setter = Expression.Lambda<Action<T>>(body, x).Compile();

      return new Binder<T>(getter, setter, newvalue, dispose);
    }

    /// <summary>
    /// Binds newvalue with getter and setter delegates
    /// </summary>
    /// <typeparam name="T">Inferred</typeparam>
    /// <param name="newvalue">the newvalue to bind</param>
    /// <param name="getter">delegate to get the value</param>
    /// <param name="setter">delegate to set the value</param>
    /// <returns>an instance to wrap use in a using construct</returns>
    public static IDisposable Bind<T>(this T newvalue, Func<T> getter, Action<T> setter)
    {
      return new Binder<T>(getter, setter, newvalue, false);
    }

    /// <summary>
    /// Binds newvalue with get/set property
    /// </summary>
    /// <typeparam name="T">Inferred</typeparam>
    /// <param name="newvalue">the newvalue to bind</param>
    /// <param name="locator">the locator, must be in the form () =&gt; instance.Property or () =&gt; Type.Property</param>
    /// <returns>an instance to wrap use in a using construct</returns>
    public static IDisposable Bind<T>(this T newvalue, Expression<Func<T>> locator)
    {
      return BuildBinder(newvalue, locator, false);
    }

    /// <summary>
    /// Binds newvalue with getter and setter delegates and disposes the value when exiting the using construct
    /// </summary>
    /// <typeparam name="T">Inferred, must be IDisposable</typeparam>
    /// <param name="newvalue">the newvalue to bind</param>
    /// <param name="getter">delegate to get the value</param>
    /// <param name="setter">delegate to set the value</param>
    /// <returns>an instance to wrap use in a using construct</returns>
    public static IDisposable With<T>(this T newvalue, Func<T> getter, Action<T> setter) where T : IDisposable
    {
      return new Binder<T>(getter, setter, newvalue, true);
    }

    /// <summary>
    /// Binds newvalue with get/set property and disposes the value when exiting the using construct
    /// </summary>
    /// <typeparam name="T">Inferred, must be IDisposable</typeparam>
    /// <param name="newvalue">the newvalue to bind</param>
    /// <param name="locator">the locator, must be in the form () =&gt; instance.Property or () =&gt; Type.Property</param>
    /// <returns>an instance to wrap use in a using construct</returns>
    public static IDisposable With<T>(this T newvalue, Expression<Func<T>> locator) where T : IDisposable
    {
      return BuildBinder(newvalue, locator, true);
    }

    // Implementation class
    sealed class Binder<T> : IDisposable
    {
      readonly bool dispose;
      readonly T orig, current;
      readonly Action<T> setter;

      internal Binder(Func<T> getter, Action<T> setter, T newvalue, bool dispose)
      {
        this.setter = setter;
        this.dispose = dispose;

        orig = getter();
        setter(newvalue);
        current = newvalue;
      }

      public void Dispose()
      {
        setter(orig);
        if (dispose)
        {
          ((IDisposable)current).Dispose();
        }
      }
    }
  }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication

Share

About the Author

leppie
Software Developer
South Africa South Africa
No Biography provided

You may also be interested in...

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150723.1 | Last Updated 4 Feb 2011
Article Copyright 2011 by leppie
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid