Scott Peterson: Variance, Thy Name is Ambiguity

本文探讨了当一个类实现同一协变或逆变接口的不同泛型类型时可能出现的歧义问题,并提出了解决方案,包括使用自定义属性来指定首选实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文作者:Scott Peterson
原文地址:[url]http://themonkeysgrinder.blogspot.com/2009/06/variance-thy-name-is-ambiguity.html[/url]


Scott同学关于co-/contravariance的几个帖子都值得一读。这篇算是比较总结性的。另外几篇的链接就在文中开头几句。
若某个类实现了同一个co-/contravariance接口的不同泛型类型,然而这几个泛型类型参数是有继承关系的,那么调用某个接口方法的时候,应该选用什么版本的问题上会有歧义。Scott同学认为应该在规范中写明一个确定的解决歧义的方法。
[quote="Scott Peterson"][align=center][size=small][b]Previously[/b]
On This Blog...[/size][/align]

"I love you, Generic Variance, and I want your babies [url=http://themonkeysgrinder.blogspot.com/2009/02/c-4-is-now.html]RIGHT NOW[/url]!"

"I think there's [url=http://themonkeysgrinder.blogspot.com/2009/04/whos-afraid-of-generic-variance.html][i]something you should know[/i][/url] about Generic Variance..."

"[url=http://themonkeysgrinder.blogspot.com/2009/05/further-generic-variance-thoughts.html]I can change him[/url]!"

[i][size=small][align=center][b]And now, the thrilling continuation...[/b][/align][/size]

I've just sent my recommendation to the ECMA 335 committee regarding the generic variance problem. I present it here for your reading pleasure:[/i]

[size=small][b]Quick Recap[/b][/size]

The following is an example of an ambiguous circumstance involving generic variance, the very sort over which we have all lost so much sleep:

.class interface abstract I<+T> {
.method public abstract virtual instance !T Foo ()
}

.class A {}
.class B extends A {}
.class C extends A {}

.class X implements I<class B>, I<class C> {
.method virtual instance class B I[B].Foo () { .override I<class B>::Foo }
.method virtual instance class C I[C].Foo () { .override I<class C>::Foo }
}

// Meanwhile, in some unsuspecting method...
I<A> i = new X ();
A a = i.Foo (); // AMBIGUITY!


[size=small][b]Give a Runtime A Bone[/b][/size]

To disambiguate such situations, we introduce a new custom attribute in the BCL. For the sake of example, let's call it System.PreferredImplementationAttribute. The PreferredImplementationAttribute is applied to a type and indicates which implementation should be selected by the runtime to resolve variance ambiguities. Our above definition of the type X would now look like this:

.class X implements I<class B>, I<class C> {
.custom instance void System.PreferredImplementationAttribute::.ctor (class System.Type) = { type(I<class C>) }
.method virtual instance class B I[B].Foo () { .override I<class B>::Foo }
.method virtual instance class C I[C].Foo () { .override I<class C>::Foo }
}


[size=small][b]New Rules[/b][/size]

With the addition of this attribute, the runtime requires that any type defined in an assembly targeting the 335 5th edition runtime which implements multiple interfaces that are variants of a common generic interface MUST specify ONE AND ONLY ONE PerferredImplementationAttribute for EACH of the potentially ambiguous common interfaces, and that each such specification of a PerferredImplementationAttribute must reference an interface implemented by the type that is a legal variant of the ambiguous common interface. In other words, all possible ambiguities MUST be disambiguated by the use of PreferredImplementationAttribute custom attributes. If a type does not satisfy these rules, the runtime MUST throw a System.TypeLoadException.

As this rule only applies to assemblies targeting the new version of the runtime, old images will continue to execute without issue. If the committee prefers, the resolution of ambiguities in old types may remain unspecified, or alphabetical priority could be codified in the spec to standardize such behavior. I would be fine leaving it unspecified.

[size=small][b]Custom Attributes vs. Metadata[/b][/size]

Ideally, I feel disambiguation information belongs in the type metadata structure rather than a custom attribute. If the committee feels that amending the metadata specification is tenable, I would recommend doing so (though I don't have any thoughts at this time on the exact logical or physical nature of such an amendment). If, on the other hand, changing the metadata spec at this point in the game is not feasible, then a custom attribute will just have to do. I see the addition of one custom attribute type to the Base Class Library as entirely justified.

[size=small][b]An Aside to Our Friends on the 334 Committee[/b][/size]

As a note to language designers targeting the runtime, I personally would consider it obnoxious if developers where burdened with the manual application of such a custom attribute. C# and other languages would do well to prohibit the direct use of the custom attribute, favoring instead a special syntax to denote the preferred implementation (the "default" keyword comes to mind in the case of C#). If this committee changes the type metadata spec to include preferred implementation information (and does not introduce a custom attribute type for that purpose), then special language syntaxes will be necessary.

[size=small][b]An Alternative[/b][/size]

In the interest of completeness, I will describe an alternate (if similar) approach to the ambiguity resolution problem. Rather than annotate types to indicate which of their interface implementations will satisfy ambiguous calls, the preferred implementation could be denoted on a per-member basis. Referring again to our original type X, this solution would modify that type thusly:

.class X implements I<class B>, I<class C> {
.method virtual instance class B I[B].Foo () { .override I<class B>::Foo }
.method virtual instance class C I[C].Foo () {
.override I<class C>::Foo
.custom instance void System.PreferredImplementationAttribute::.ctor ()
}
}


The member I[C].Foo is annotated with the System.PreferredImplementationAttribute, indicating that it will be selected by the runtime to fulfill otherwise ambiguous calls to I<T>.Foo. Note that in this solution the constructor to the PerferredImplementationAttribute type is parameterless. The runtime ensures that for EACH of the members of an interface which is the common variant of two or more of the interfaces implemented by a type, ONE AND ONLY ONE of the implementations for that member is flagged as "preferred."

Per-member preference definition affords developers more control but costs runtime implementers time, effort, and simplicity. I also don't envision many scenarios when developers would desire per-member control over implementation preference. I personally find this approach less tasteful than the per-interface solution but I mention it here, as I said, for completeness.

[size=small][b]One More Thing...[/b][/size]

There remains a situation on which there are varied opinions:
.class interface abstract I<+T> {
.method public abstract virtual instance !T Foo ()
}

.class A {}
.class B extends A {}

.class X implements I<class A> {
.method virtual instance class A I[A].Foo () { .override I<class A>::Foo }
}

.class Y extends X implements I<class B> {
.method virtual instance class B I[B].Foo () { .override I<class B>::Foo }
}

// Meanwhile, in some unsuspecting method...
I<A> i = new Y ();
A a = i.Foo ();


In this situation I<A>::Foo is called on an object of type Y. There is an implementation of I<A>::Foo in Y's type hierarchy (X::I[A].Foo), but there is also an available implementation which is a legal variant of I<A> in Y itself (Y:I[B].Foo). Does the runtime favor the exact implementation, or the more derived variant implementation? I don't have strong feelings on the matter, but my slight preference is for favoring the exact implementation.

The runtime is deciding on behalf of the developer which implementation is most appropriate. It could be argued that an exact implementation, wherever it is to be found the type hierarchy, is more appropriate than a variant implementation.

Also - and this is an implementation detail which should not outweigh other considerations but may be useful to keep in mind if all other things are equal - Mono stores a type's implemented interfaces in a binary tree, meaning that finding an exact implementation is an O(log n) worst-case operation, whereas finding a legal variant interface among a type's implemented interfaces is an O(n) worst-case operation (all interfaces must be examined to see if a legal variant exists among them). I haven't heard of any way to do O(log n) (or better) lookup of variants. With such popular types as IEnumerable`1 becoming variant, the superior time complexity could make a difference.[/quote]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值