http://www.blogcn.com/User8/flier_lu/index.html?id=3968327
briankel 的 blog 上有个有趣的讨论,《C# 'that' keyword》。讨论如果在 C# 中增加 that 关键字,将会表示什么意思 :P
简单分析可以发现,that 关键字应该是与 this 关键字对应或者至少平级的,因此在语法和语义上应该尽量保持同一性。也就是说加入的 that 关键字应该也会用于表示某种
隐式的对象引用,与之类似的还有 base 关键字,都是隶属于 Access Keywords。
因此 Matthew W. Jackson 和 anony 等人的意见是将此关键字用于 with 语句的隐式上下文引用,如
public int ThatExample<T>(T x, T y) { with(System.Collections.Comparer<int>.Default) { if(that.Compare(x, y) > 0) return that.GetHashCode(x); else return that.GetHashCode(y); } }
| |
这样的好处是可以明确获得在 with 范围内的隐式对象引用,而且语义上来说跟 this 的隐式语义很类似。同时能够提供在 with 范围内,将隐式引用对象作为参数传递给其他方法的能力,这是 pascal 等 with 语法中所不具备的能力,如
with new Foo() { WhateverProperty = "property"; IntProperty = 1;
//call Whatever with the Foo we just made //before calling entering the block Bar.Whatever(that); }
| |
而除了对象自身作用域内访问隐式对象这一特性外,this 关键字实际上还有一个
隐式参数传入的特性。也就是说在对象方法调用时将对象本身的引用,通过 this 隐式传入到方法中。与这一特性对应的当数 pascal 中的 result 关键字,显式访问函数的返回值。因此 Matthew W. Jackson 建议通过如下方式使用 that 关键字:
public int ThatExample<T>(T x, T, y) { IComparer<T> comparer = Comparer<T>.Default;
if(comparer.Compare(x, y) > 0) that = comparer.GetHashCode(x); else that = comparer.GetHashCode(y); }
| |
这样可以让使用者无需手工构造返回值实例,而由编译器承担这个薄记工作。同时这也是一种很好的 compiler hint,在进行 JIT 优化时能够避免不必要的临时变量使用,如果 C++ 这种值语义为主的系统能够增加这一特性就好了,呵呵。
此外还有一些建议是将之用于与 this 相对的用途,如在操作符重载中指向双目操作符的另一个对象,避免使用 rhs 这样的变量,而由编译器隐式处理,减少薄记工作。
或者用于一些较为特殊的用途,如在 clone 函数里面指向 clone 操作的目标对象
class X : ICloneable { private SomeOtherDeepCopyableClass data;
[UsesCopy] public override object Clone() { that.data=(SomeOtherDeepCopyableClass)data.Clone(); } }
| |
或者在 foreach 中作为限定符进行过滤,类似 C# 2.0 中对泛型支持时的限定
foreach (MyType a that IBlarg in myCollection) { // 只获得实现了 IBlarg 接口的 MyType 类型 }
| |
不过这些用法感觉实在是有些大材小用了,呵呵
有趣的是 Matthew W. Jackson 在第三种意见里面,提出通过 that 关键字提供对 double virutal dispatch 的支持。这里的 double virutal dispatch 是一种在 C++ 中模拟 Multi-Methods 语义的模式,因为 C++ 不像 CLOS 等语言直接提供此类支持。例如我们在 C++ 中定义 Shapre, Rectangle 和 Circle 三种类型,分别对自己与其他类型图元的重叠操作提供支持:
class Shape {
virtual bool intersect(const Shape* s);
};
class Rectangle : public Shape {
virtual bool intersect(const Shape* s); virtual bool intersect(const Circle* s);
};
class Circle : public Shape {
virtual bool intersect(const Shape* s); virtual bool intersect(const Rectangle* s);
};
| |
我们希望两个 Shape 子对象在进行重叠操作时,能够根据两者的类型调用合适的处理方法,如
Shape* circle = new Circle(); // upcast to Shape Shape* rectangle = new Rectangle(); // upcast to Shape circle->intersect(rectangle); // Calls Circle::intersect(Shape*)
| |
这里的 intersect 方法调用,因为参数被静态绑定到 Shape 类型上,因此无法调用我们希望的 Circle::intersect(Rectangle *),而是调用 Circle 类型重载 Shape 类型的 vtable 中 Shape::intersect(Shape *) 方法的 Circle::intersect(Shape*)。也就是说,只有一次通过 Circle 对象 vtable 进行的 virtual dispatch,是无法满足我们希望的对操作源对象和目标对象 double virtual dispatch 的。因为 C++/C#/Java 这些所谓静态类型语言都无法提供这种语义的直接支持,我们不得不通过 double virtual dispatch 模式来模拟。如在 C# 中定义如下类型:
interface Shape { void intersect(Shape shape); void intersect(Circle circle); void intersect(Rectangle circle); }
class Circle : Shape { public void intersect(Shape shape) { Console.Out.WriteLine("Circle::intersect(Shape)"[img]/images/wink.gif[/img];
shape.intersect(this); }
public void intersect(Circle circle) { Console.Out.WriteLine("Circle::intersect(Circle)"[img]/images/wink.gif[/img]; }
public void intersect(Rectangle circle) { Console.Out.WriteLine("Circle::intersect(Rect)"[img]/images/wink.gif[/img]; } }
class Rectangle : Shape { public void intersect(Shape shape) { Console.Out.WriteLine("Rect::intersect(Shape)"[img]/images/wink.gif[/img];
shape.intersect(this); }
public void intersect(Circle circle) { Console.Out.WriteLine("Rect::intersect(Circle)"[img]/images/wink.gif[/img]; }
public void intersect(Rectangle circle) { Console.Out.WriteLine("Rect::intersect(Rect)"[img]/images/wink.gif[/img]; } }
class EntryPoint { [STAThread] static void Main(string[] args) { Shape circle = new Circle(); Shape rect = new Rectangle();
circle.intersect(rect); } }
| |
Shape 类型中预先就定义好可能存在的分发目标,然后通过在 Shape::intersect 方法的重载函数中,调用 shape.intersect(this); 进行二次分发。因为在此时,this 指向的类型是源操作对象的真实类型。也就是说,先通过源对象的 vtable 进行一次 virtual dispatch 之后,再在源对象的 Shape::Intersect 方法重载实现中,调用目标对象的 intersect 方法,通过目标对象 vtable 进行第二次 virtual dispatch,进而通过 double virtual dispatch 模式完成我们期望的 Multi-Methods 支持。允许结果如下
以下为引用:
Circle::intersect(Shape) Rect::intersect(Circle)
|
但这样一来引入了几个新的问题。首先,作为基础的抽象 Shape 接口必须与实现其接口的子类型进行绑定,每次添加新类型都必须修改几乎所有类型;其次,每个 Shape 接口的子类型都必须重载其 Shape::intersect(Shape shape) 方法的实现,来完成二次分发,虽然其实现代码语法完全相同;最后,每个新增类型都会要求为所有已有类型添加新的方法,最后方法的数量会根据类型的多少呈指数上升,并且无法收敛。
因此如果希望在 C# 中真正大量使用这种 double virtual dispatch 的语法来模拟不太可能被直接支持的 Multi-Methods 语义,必要的编译器一级的支持是必不可少的。例如通过 that 关键字,指向 this 关键字指向的同一对象,只不过其类型绑定到对象的实际类型上。这样一来就可以将每个子类型中的二次分发函数 Shape::intersect(Shape shape) 的重载消除,只保留 Shape 类型中的唯一实现,如
abstract class Shape { virtual public void intersect(Shape shape) { shape.intersect(that); } }
class Circle : Shape { override public void intersect(Circle circle) { Console.Out.WriteLine("Circle::intersect(Circle)"[img]/images/wink.gif[/img]; }
override public void intersect(Rectangle circle) { Console.Out.WriteLine("Circle::intersect(Rect)"[img]/images/wink.gif[/img]; } }
classs Rectangle : Shape { // }
| |
这里的 shape.intersect(that); 语句的语义,实际上等同于
MethodInfo method = shape.GetType().GetMethod("intersect", new Type[] { this.GetType() }); method.Invoke(shape, new object[] { this });
| |
而且也无需在根对象上绑定所有的子类型,由执行时根据传入对象的类型完成自动分派。为了处理子对象可能没有专用处理函数的问题,还可以修改其语义为:
Type type = shape.GetType(); MethodInfo method = null;
while(type != null && method == null) { method = type.GetMethod("intersect", new Type[] { this.GetType() });
type = type.BaseType; }
if(method != null) method.Invoke(shape, new object[] { this });
| |
这样一来子类型就可以任意定义对其他类型的处理方法,如 Circle::intersect(Rectangle);如果不像定义,或者不知道另外一个子类型的存在,可以通过一个 Circle::intersect(Shape) 安全处理所有例外情况。例如下面代码中,Rectangle 类型没有专门针对 Ellipse 类型的处理函数,但有针对 Ellipse 类型父类 Circle 的处理函数,故而调用结果是 Rect::intersect(Circle)。
abstract class Shape { virtual public void intersect(Shape shape) { //shape.intersect(that);
Type type = shape.GetType(); MethodInfo method = null;
while(type != null && method == null) { method = type.GetMethod("intersect", new Type[] { this.GetType() });
type = type.BaseType; }
if(method != null) method.Invoke(shape, new object[] { this }); } }
class Circle : Shape { virtual public void intersect(Circle circle) { Console.Out.WriteLine("Circle::intersect(Circle)"[img]/images/wink.gif[/img]; }
virtual public void intersect(Rectangle circle) { Console.Out.WriteLine("Circle::intersect(Rect)"[img]/images/wink.gif[/img]; }
virtual public void intersect(Ellipse ellipse) { Console.Out.WriteLine("Circle::intersect(Ellipse)"[img]/images/wink.gif[/img]; } }
class Ellipse : Circle { override public void intersect(Circle circle) { Console.Out.WriteLine("Ellipse::intersect(Circle)"[img]/images/wink.gif[/img]; }
override public void intersect(Rectangle circle) { Console.Out.WriteLine("Ellipse::intersect(Rect)"[img]/images/wink.gif[/img]; }
override public void intersect(Ellipse ellipse) { Console.Out.WriteLine("Ellipse::intersect(Ellipse)"[img]/images/wink.gif[/img]; } }
class Rectangle : Shape { public void intersect(Circle circle) { Console.Out.WriteLine("Rect::intersect(Circle)"[img]/images/wink.gif[/img]; }
public void intersect(Rectangle circle) { Console.Out.WriteLine("Rect::intersect(Rect)"[img]/images/wink.gif[/img]; } }
class EntryPoint { [STAThread] static void Main(string[] args) { Shape circle = new Ellipse(); Shape rect = new Rectangle();
circle.intersect(rect); } }
| |
除此之外,个人觉得还可以通过 that 处理泛型支持时的一些问题,明天有空在写几个例子说明一下