C# 中的多态扩展访问者






4.45/5 (7投票s)
.NET 4 最终允许定义多态扩展访问者
引言
我喜欢(双分派)访问者模式来遍历对象树。但有些代码没有提供访问者接口。因此,我已经在 .NET 3.5 中寻找一种简单而强大的方法来实现扩展的访问者模式。这失败了,因为调用的 Visit
方法是基于 static
类型而不是动态类型选择的。
现在 .NET 4 框架及其动态类型功能解决了这个问题。
在本文中,我假设读者了解双分派访问者模式(例如,参见 http://en.wikipedia.org/wiki/Visitor_pattern)。
Using the Code
此访问者模式的核心是以下代码
// General Accept extension method.
public static void Accept(this object objItem, Visitor objVisitor)
{
try
{
((dynamic)objVisitor).Visit((dynamic)objItem); // polymorphic Visit call
}
catch (RuntimeBinderException excException)
{
if (objVisitor.Rethrow) throw;
objVisitor.Fallback(objItem, excException);
}
}
将 objItem
设为动态对象,可以强制多态地调用最匹配的 Visit
方法。它采用完全匹配的方法,或者匹配 objItem
最接近基类的方法。
将 objVisitor
设为动态对象,允许定义具有在访问者类层次结构的任何级别上定义的任何附加 Visit
方法的多态访问者类层次结构。无需在所有类中添加新的 Visit
方法;您也可以仅在叶类中添加额外的 Visit
。
相应的根 Visitor
类如下所示
// extension visitor basics
public abstract class Visitor
{
// controls if a missing Visit method throws an exception (true)
// or if it executes the Fallback method (false).
public bool Rethrow { get; private set; }
// bRethrow = true: rethrow if no matching visit method found, false:
// call Fallback method instead
protected Visitor(bool bRethrow)
{
Rethrow = bRethrow;
}
// fall back method that is called if no matching visit method is found
public abstract void Fallback(object objItem, Exception excException);
}
用户定义的 Visitor
定义所需的 Visit
方法,例如:
// base visitor
public class BaseVisitor: VisitorExtension.Visitor
{
#region construction and basic framework
// The visitor calls Fallback if no matching Visit method is found.
public BaseVisitor()
: base(false)
{
}
// generic fall back if no visit method is found
public override void Fallback(object objItem, Exception excException)
{
Console.WriteLine("{0}.Fallback({1}, {2})",
this.GetType().Name, objItem.GetType().Name, excException.GetType().Name);
}
#endregion
#region User defined Visit methods
// visit method implementations
public virtual void Visit(Base objItem)
{
Console.WriteLine("BaseVisitor.Visit<Base>({0})", objItem.GetType().Name);
}
public virtual void Visit(Derived_A objItem)
{
Console.WriteLine("BaseVisitor.Visit<Derived_A>({0})", objItem.GetType().Name);
}
#endregion
}
// Specific visitor
public class SpecificVisitor : BaseVisitor
{
// visit all elements and call the Extension method Accept
// which calls polymorphically the appropriate Visit method.
virtual public void Visit(Container objItem)
{
Console.WriteLine("SpecificVisitor.Visit<Container>({0})",
objItem.GetType().Name);
foreach (var objElement in objItem.Elements)
{
objElement.Accept(this);
}
}
}
访问具有多态数据的容器可以按预期工作
class Program
{
static void Main(string[] args)
{
Container c = new Container();
c.Elements.Add(new Base());
c.Elements.Add(new Derived_A());
c.Elements.Add(new Derived_B());
c.Elements.Add(new Derived_A_A());
// SpecificVisitor provides the following Visit methods
// - Visit(Base)
// - Visit(Derived_A)
// - Visit(Container)
BaseVisitor objVisitor = new SpecificVisitor();
c.Accept(objVisitor); // visits polymorphically the elements
12345.Accept(objVisitor); // triggers Fallback
}
}
// test classes
public class Base
{
}
public class Derived_A : Base
{
}
public class Derived_B : Base
{
}
public class Derived_A_A : Derived_A
{
}
public class Container
{
public IList<Base> Elements { get; set; }
public Container()
{
Elements = new List<Base>();
}
}
关注点
我很高兴 .NET 4 终于允许这样做——在 .NET 3.5 中,我非常沮丧,因为我无法以一种表达的方式做到这一点。
历史
- 2010-09-26:初始版本
- 2010-09-27:改进了代码格式