Flier's Sky

天空,蓝色的天空,眼睛看不到的东西,眼睛看得到的东西

导航

如果 C# 有 that 关键字会怎样

Posted on 2004-09-25 21:44  Flier Lu  阅读(2301)  评论(5编辑  收藏  举报
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 处理泛型支持时的一些问题,明天有空在写几个例子说明一下