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 处理泛型支持时的一些问题,明天有空在写几个例子说明一下
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述