在客户端, Slice 映射到C# 接口,后者的成员函数与前者的操作相对应。考虑下面的简单接口:
interface Simple {
void op();
};
Slice 编译器生成以下定义,供客户使用:
public interface SimplePrx : Ice.ObjectPrx {
void op();
void op(Ice.Context __context);
}
你可以看到,编译器生成了一个代理接口 SimplePrx。一般而言,生成的代理接口的名字是 <interface-name>Prx。如果接口是嵌在模块M 中的,生成的类就是M命名空间的一部分,所以受到完全限定的名字是M.<interface-name>Prx。
在客户的地址空间中, SimplePrx的实例是 “远地的服务器中的Simple接口的实例”的 “本地大使”,叫作代理实例。与服务器端对象有关的所有细节,比如其地址、所用协议、对象标识,都封装在该实例中。
注意,SimplePrx继承自 Ice.ObjectPrx。这反映了这样一个事实:所有的Ice 接口都隐式地继承自 Ice::Object。
对于接口中的每个操作,代理类都有一个同名的成员函数。就前面的例子而言,我们会发现操作op 已经被映射到成员函数op。还要注意,op是重载的:op的第二个版本有一个参数__context,类型是Ice.Context。Ice run time 用这个参数存储关于请求的递送方式的信息;你通常并不需要使用它 .
因为所有的<interface-name>Prx类型都是接口,你不能实例化这种类型的对象。相反,代理实例总是由 Ice run time 替客户实例化,所以客户代码永远都不需要直接实例化代理。Ice run time 给出的代理引用的类型总是 <interface-name>Prx;这种接口的具体实现是Ice run time 的一部分,与应用代码无关。
Null值说明这是一个空代理,空代理用来指示一个“不在这里”的代理,既没有这个对象。
Ice.ObjectPrx接口
所有Ice 对象的最终祖先都是Object,所以所有代理都继承自Ice.ObjectPrx。ObjectPrx提供了一些方法:
Namespace Ice;
public interface ObjectPrx {
Identity ice_getIdentity();
int ice_hash();
bool ice_isA(string id);
string ice_id();
void ice_ping();
int GetHashCode();
bool Equals(object r);
// Defined in a helper class:
//
public static bool Equals(Ice.ObjectPrx lhs,
Ice.ObjectPrx rhs);
public static bool operator==(ObjectPrx lhs,
ObjectPrx rhs);
public static bool operator!=(ObjectPrx lhs,
ObjectPrx rhs);
// ...
}
}
注意,static类型的方法并不是在Ice.ObjectPrx中定义的,而是在一个会变成代理实例基类的helper类中定义的。然而,这部分工作由C# Mapping进行,在概念上,这些静态方法仍属于Ice.ObjectPrx,所以仍把它们放在这里讨论。
这些方法的行为如下:
* ice_getIdentity
这个方法返回的是代理所代表的对象的标识。Ice 对象标识的 Slice类型是:
module Ice {
struct Identity {
string name;
string category;
};
};
想知道两个代理代表的是否是同一个对象,首先获取每个对象的标识,然后对其进行比较:
Ice.ObjectPrx o1 = ...;
Ice.ObjectPrx o2 = ...;
Ice.Identity i1 = o1.ice_getidentity();
Ice.Identity i2 = o2.ice_getidentity();
if (i1.equals(i2))
// o1 and o2 denote the same object
else
// o1 and o2 denote different objects
* ice_hash
这个方法返回代理的哈希键。(和GetHashCode完成一样的功能)
* ice_isA
这个方法确定代理所代表的对象是否支持特定接口。 ice_isA的参数是一个类型ID。例如,要想知道一个ObjectPrx 类型的代理代表的是否是 Printer对象,我们可以编写:
Ice.ObjectPrx o = ...;
if (o != null && o.ice_isA("::Printer"))
// o denotes a Printer object
else
// o denotes some other type of object
注意,在调用 ice_isA方法之前,我们先测试了代理是否为null。这样,如果代理为null,就不会引发NullPointerException 了。
* ice_id
这个方法返回代理所代表的对象的类型ID。注意,返回的类型是实际对象的类型,其派生层次可能比代理的静态类型更深。例如,如果我们有一个 BasePrx类型的代理,其静态的类型ID 是 ::Base, ice_id的返回值可能会是 ::Base,也可能是派生层次更深的对象的类型ID,比如 ::Derived。
* ice_ping
这个方法为对象提供了基本的可到达测试。如果对象可以从物理上联系到 (也就是说,对象存在,它的服务器在运行,并且可到达),调用就会正常完成;否则,它就会抛出异常,说明对象为何不能到达,比如 ObjectNotExistException or ConnectTimeoutException。
* equals
这个操作比较两个代理是否相等。注意,这个操作会比较代理的所有方面,比如代理的通信端点。这意味着,一般而言,如果两个代理不相等,那并不说明它们代表的是不同的对象。例如,如果两个代理代表的是同一个Ice 对象,但所用传输端点不同,那么即使它们代表的是同一个对象, equals也会返回 false。
注意,ObjectPrx还有另外一些方法,在此没有给出。这些方法提供了不同的调用分派方式,同时还可以访问对象的facets.
代理助手
对于每个 Slice 接口,除了代理接口以外, Slice2C#编译器还会创建一个助手类:对于Simple接口,所生成的助手类的名字是SimplePrxHelper。助手类含有两个有意思的方法:
public class SimplePrxHelper: Ice.ObjectPrxHelper implements SimplePrx {
public static SimplePrx checkedCast(Ice.ObjectPrx b);
public static SimplePrx checkedCast(Ice.ObjectPrx b,
Ice.Context ctx);
public static SimplePrx uncheckedCast(Ice.ObjectPrx b)
// ...
}
checkedCast和uncheckedCast方法实现的都是向下转换:如果传入的代理是 Simple类型的对象的代理,或者是Simple 的派生类型的对象的代理,这两个方法就会返回一个非null 引用,指向的是一个SimplePrx类型的代理;而如果传入的代理代表的是其他类型的对象 (或者传入的代理为null ),返回的就是null 引用。
对于任何类型的代理,你都可以 checkedCast来确定其对应的对象是否支持指定的类型,例如:
Ice.ObjectPrx obj = ...; // Get a proxy from somewhere...
SimplePrx simple = SimplePrxHelper.checkedCast(obj);
if (simple != null)
// Object supports the Simple interface...
else
// Object is not of type Simple...
注意,checkedCast会联系服务器。这是必要的,因为只有服务器情况中的代理实现确切地知道某个对象的类型。所以,checkedCast可能会抛出 ConnectTimeoutException或ObjectNotExistException(这也解释了为何需要助手类:Ice run time 必须联系服务器,所以我们不能使用C#的向下转换)。
与此相反,uncheckedCast 不会联系服务器,而是会无条件地返回具有所请求的类型的代理 。但是,如果你要使用uncheckedCast,你必须确定这个代理真的支持你想要转换到的类型;而如果你弄错了,你很可能会在调用代理上的操作时,引发运行时异常。对于这样的类型失配,最后可能会引发 OperationNotExistException,但也有可能引发其他异常,比如整编异常。而且,如果对象碰巧有一个同名的操作,但参数类型不同,则有可能根本不产生异常,你最后就会把调用发送给类型错误的对象;这个对象可能会做出非常糟糕的事情。为了说明这种情况,考虑下面的两个接口:
interface Process {
void launch(int stackSize, int dataSize);
};
// ...
interface Rocket {
void launch(float xCoord, float yCoord);
};
假定你期望收到的是 Process对象的代理,并且要用 unchecedCast对这个代理进行向下转换:
Ice.ObjectPrx obj = ...; // Get proxy...
ProcessPrx process= ProcessPrxHelper::uncheckedCast(obj); // No worries...
process.launch(40, 60); // Oops...
如果你收到的代理实际上代表 Rocket对象, Ice run time 无法检测到这个错误:因为int和float 的尺寸相同,而Ice 协议在线路上没有标记数据的类型,于是Rocket::launch 的实现就会误把传入的整数当作浮点数。
公平地说,这个例子是人为制造的。要让这样的错误在运行时不被注意到,两个对象都必须有一个同名的操作,而且,传给操作的运行时参数整编后的总尺寸,必须与服务器端的解编代码所期望的字节数相吻合。在实践中,这相当罕见,错误的uncheckedCast通常会导致运行时异常。
关于向下转换的最后一个警告:你必须使用checkedCast或uncheckedCast对代理进行向下转换。如果你使用了C#的强制转换,其行为是不确定的。
对象标识和代理比较
就你上面提到的,代理类提供了Equals方法,Equals方法会比较代码的所有信息,这意味着,不光对象标识符要一样,其它的一些方面也比较一样,比如协议和传输端点。另外,Equals( 以及 == 和!=)比较代理标识符,而不是对象标识符。一个常见的错误是像下面这样写代码:
Ice.ObjectPrx p1 = ...; // Get a proxy...
Ice.ObjectPrx p2 = ...; // Get another proxy...
if (p1.Equals(p2)) {
// p1 and p2 denote different objects // WRONG!
} else {
// p1 and p2 denote the same object // Correct
}
尽管p1和p2是不同的,但它们有可能代理同一个ice对象。这是因为,有可能p1,p2的对象标识符相同,但它们使用了不同的协议去连接目标对象。同样,有可能p1,p2使用了相同的协议,但是代理了不同的对象。(这是因为可以使用几个传输端点连接同一个Ice对象)。换名话说,如果我们用Equals方法判定两个代理相等,我们知道它们代理的是一个对象,因为它们所有的方面都相同。但是如果用Equals方法判定两个代理不相等,我们什么也不知道,它们有可能代理同一个对象,也有可能不是。
因此,比较两个代理的对象标识符,我们必须借助于Ice.Util类的方法:
public sealed class Util {
public static int proxyIdentityCompare(ObjectPrx lhs,
ObjectPrx rhs);
public static int proxyIdentityAndFacetCompare(ObjectPrx lhs,
ObjectPrx rhs);
// ...
proxyIdentityCompare方法允许你正确的进行代理标识符的比较:
Ice.ObjectPrx p1 = ...; // Get a proxy...
Ice.ObjectPrx p2 = ...; // Get another proxy...
if (Ice.Util.proxyIdentityCompare(p1, p2) != 0) {
// p1 and p2 denote different objects // Correct
} else {
// p1 and p2 denote the same object // Correct
}
如果两个代码的标识符是相等的,则方法返回0,如果p1<p2返回-1。如果p1>p2则返回1。
proxyIdentityAndFacetCompare方法完成相似的功能,但是它在比较标识符的同时,还会比较facet。
C#映射还在Ice命名空间下提供了两个帮助类,它们主要根据标识符或是标识符+facet将一个代理类插入到hashtable中或可排序的集合中。
public class ProxyIdentityKey : System.Collections.IHashCodeProvider,
ystem.Collections.IComparer {
public int GetHashCode(object obj);
public int Compare(object obj1, object obj2);
}
public class ProxyIdentityFacetKey: System.Collections.IHashCodeProvider,
System.Collections.IComparer {
public int GetHashCode(object obj);
public int Compare(object obj1, object obj2);
}