65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的动态绑定

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (26投票s)

2011年2月4日

公共领域

3分钟阅读

viewsIcon

93265

downloadIcon

802

演示 C# 中的动态绑定实现。

引言

本文将向您展示如何在 C# 中模拟动态绑定。

背景

要理解动态绑定的含义,可以参考 SRFI 39,它是 R5RS Scheme 的动态绑定机制的实现。动态绑定也经常用于 LISP 中。另一个常用的动态绑定是 JavaScript 中的 'this' 关键字。有关深入的解释,请阅读 Christian Queinnec 的 LISP in Small Pieces 第 2 章。

要理解此代码的作用(在动态绑定方面),请考虑以下伪 C# 代码段

var orig-value = getvalue(location);
setvalue(location, new-value);
try
{
  body
}
finally
{
  setvalue(location, orig-value);
}

首先,它获取并存储原始值。 然后,它将位置(属性或字段或 getter/setter 方法对)的值设置为新值。 一旦执行离开主体部分,原始值将被无条件地恢复。 这意味着即使存在异常,值也能被忠实地恢复。

另一个方面是动态绑定可以彼此嵌套。 例如,我们可以将相同的构造放置在主体部分中。

这说明了动态绑定的本质,以及它在本文配套代码中的实现方式。

Using the Code

该代码采用扩展方法的形式,封装在一个类中。 要使用该代码,您可以将该类复制到任何位置,只需包含包含的命名空间即可。

“API”公开了两个方法,每个方法都有两个重载。

Bind 方法执行简单的动态绑定,而 With 方法允许在退出动态绑定的主体时自动释放动态绑定。 这两个方法都应在 C# 的 using 构造的表达式部分中调用。

请使用以下内容作为上述和整个文章中使用的术语的指南

using (expression)
{
  body
}

上述每种方法都提供了一个额外的重载方法,该方法采用 Expression<Func<T>> 参数,方便起见。 这允许人们为属性和字段构建 getter 和 setter 委托。

以下是公开的方法的方法签名和文档

/// <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)
/// <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 () => instance.Property or () => 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)
/// <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
/// <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
/// () => instance.Property or () => 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

使用示例

附加的代码包含几个示例,以说明此动态绑定实现的使用。 在每个示例中,退出 using 构造的主体后,原始值将被恢复。

以下类声明将在一些示例中使用

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

实例属性示例

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);

静态属性示例

Console.WriteLine(Console.ForegroundColor);

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

Console.WriteLine(Console.ForegroundColor);

局部变量示例

var i = 0;

Console.WriteLine(i);

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

Console.WriteLine(i);

静态字段示例

Console.WriteLine(TestClass.StaticField);

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

Console.WriteLine(TestClass.StaticField);

带有示例扩展的 Getter 和 Setter 方法对示例

此示例使用了名为 AsConsoleOutput() 的辅助扩展方法

/// <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);
}

用法如下

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");

关注点

该实现大量使用了 LINQ 表达式和来自 System.Reflection.EmitDynamicMethod,以提高性能。

该实现只有 152 行代码,包括 XML 文档注释、宽松的空白和错误检查。

顺便说一句,F# 名人 Tomas Petricek 在我“宣布”我的意图后,今天刚发布了一个 F# 中的实现。 该代码段可以在 http://www.fssnip.net/2s 查看。 请注意,我公然窃取了他的 ConsoleColor 示例 :)。

历史

  • 2011 年 2 月 4 日:初始版本
© . All rights reserved.