【转】 c#,c++,Delphi泛型比较(Comparing C#, C++, and Delphi (Win32) Generics)

原文连接

http://blogs.teamb.com/craigstuntz/2009/10/01/38465/

Craig Stuntz's Weblog

译文连接

盘古开发论坛翻译

index.asp?id=2037

google翻译并整理

C#、C + +和Delphi都有一个泛型类型和方法的语言特性。虽然这三种语言是静态类型的,他们用非常不同的方式实现泛型。我将比较无论是在语言功能和执行方面的简单的差异。我相信,delphi prism基本上也会仿制C#的泛型,因此你会看到,跟Delphi/Win32的泛型有些不同。

我先申明,虽然这三个系统的工作有所不同,我没有看到任何一个的设计有压倒性的优势。一般来说,你可以根据你的需要来选择三种环境。

我写这篇文章并不是说明某个系统比其他系统好,而是要指出,在实现中的一些微妙之处。

在我开始之前,我要感谢巴里凯利,他对我的这篇文章的第一稿的有益反馈。

编译实例化

每一个泛型类型通过工程的实施过程分为两个步骤。首先,对于一个特定的类型或方法定义一个通用型的“占位”,稍后将被替换。然后(具体时间根据语言而定),该类型就是“实例”了。请注意,实例化泛型类型和实例化对象有很大的不同。前者发生在编译器,而后者则在运行时发生。

实例化时触发一些使用泛型类型或特定类型参数的方法的代码,意味着在一般的定义,类型或传递时,一般是使用在传值的基础上,具体实施,就是生成不同的机器代码。实例是真假泛型类型和使用非泛型类型与转换的最重要的差别之一。最后,针对不同类型参数实例化生成不同的机器代码。

在C#和Delphi,有专门致力于执行泛型类型和方法的语言功能。另一方面,C + +,通过“模板”的语言特性可以用来实现在许多泛型类型和

方法,以及其他事情。使用模板它甚至可以做到通用编程,因此C + +程序员称之为“元编程。”


当C + +使用模板的时候,他需要模板源代码。这是因为编译器不实际将模板作为一个单独的实体进行编译,而是作为“基础”,只编译他的

实例。C + +编译器实际上是做代码生成,代该类型的占位符的类型参数(或值),生成新的代码的实例。更新:莫里茨布泰尔阐述他对这个

职位的优秀评论。你应该阅读完整的评论,但短期的版本是模板的方式,可能会导致编译的代码中使用模板出现(从编译器的错误消息)不正确的错误,是模板本身的错误。此外,大多数C + +编译器的实现使得这个问题变得必要的,以实现C + +标准差。

另一方面,Delphi和C#,泛型类型或方法的代码可以单独编译。因此,你可以编译一个库,它包含一个泛型类型,后来再编译可执行文件,

它使用该类型的一个实例,并以二进制库的方式引用,而不是到该库的源代码。

另一种方式来思考此不同的是,在C + +中,模板直到用到他前将不会被编译。另一方面,Delphi和C#中的泛型类型或方法必须编译才能使用。

在Delphi中,编译器将使用类似内联函数的方法展开相关功能。这将导致编译器来存储在已编译的DCU内的,并且包含泛型类型参数的抽象语法树中的相关位。当使用泛型类型的代码被编译时,这个抽象语法树位是只读的,抽象语法树中的代码,它使用通用的类型,这样当产生机器代码时候,产生新的“复合型 “的抽象语法树,看起来,新合成的代码代码,就像类型和类型参数定义的”硬编码“。而不是在编译链接到原来的DCU中,它使用自己的代码代替泛型类型并实例化到自己的新的DCU代码中。

由于Delphi编译器的泛型的实例化方法不执行同一地区的内联,你在使用一个泛型的方法,或一个泛型类型的时候有一些限制。像内联方法,这些方法不能包含的ASM。此外,调用这些方法不能被内联。这些限制是实施限制,而不是语言的设计,理论上可以在编译器的未来版本中删除。

C#泛型使用。NET框架2.0 +,他提供泛型的本地支持。 C#编译器会指定IL中的一个泛型来使用某种类型的参数。.NET框架通过使用一个实例化来对应任何引用,定制实例化对应值类型 来实现泛型支持。 (不要混淆“实例化”与“实例”在前面的句子。。他们的意思在这方面完全不同的事情,通常有一个实例化有很多实例)这是因为对一个引用类型的引用是一样的大小,而值类型可以有很多不同的大小。后来,IL 将实时编译成机器代码,而且,与编译的C ++或Delphi代码不同,类型不存在真正的机器代码中。在。NET中,泛型类型的实例化和JITting是两个不同的操作。

所以,一个重要的区别是实现泛型实例化时发生。在C + +编译它发生得很早,Delphi编写的则稍晚,.NET编译的则尽可能晚。

定制专业化

另外一个很重要的区别是,C + +允许自定义实例,即定制,包括定制值。用C#和Delphi,另一方面,实例化泛型的唯一方法就是一个确定的数据类型参数。因为C + +允许自定义实例,程序员很容易的就可以写一个方法的不同的实现,例如,不同的整数值。 就象运算符重载,这是一个强大的功能,它需要大量的自我克制,避免滥用。

约束

Delphi和C#都具有一个泛型的约束的功能,它允许/需要一个泛型方法的类型限制哪些类型的参数值可通过。例如,泛型类型,在这两种语言中对一些需要迭代的数据列表可以要求类型参数支持IEnumerable。这使得泛型类型开发人员可以很清楚表达他们使用该类型的意图。它还允许IDE来提供代码完成/智能感知的泛型类型定义的参数。此外,它允许泛型的用户不必编译他们的代码就可以很容易传递一个泛型参数的值。

在C + +,另一方面,目前尚未有任何这样的特征。一个更强大的/复杂的功能,称为“oncepts”,曾经讨论,但在C + +0 x中最终被删除

一个缺乏约束的含义是,C + +模板是鸭子类型。如果一个泛型方法调用的一些方法,Foo类型作为泛型类型参数传递,模板把他编译的和传递的参数类型一样长,无论他在哪里或如何定义的。


协变和逆变

比方说,我有一个函数,它接受一个类型为IEnumerable <TParent>的参数。我可以传递一个类型为IEnumerable <TChild>参数给他吗?如果参数的类型为Tlist<TParent> 代替IEnumerable<TParent>情况会怎么样呢?或者,如果泛型是函数的返回结果,而不是函数的参数情况又怎么样呢?这些问题的正式名称是协变和逆变。确切的细节太复杂,但上面的例子总结了最常见的时候。

Delphi泛型和C + +模板不支持协变和逆变。因此,对上述问题的答案是不,不,不,虽然有,当然,解决方法一样复制到一个新的列表的数据,。在C#4.0中,函数参数和结果可以声明为可协变和逆变,所以上面的例子,可在适当情况下工作。 “在适当情况下”指的不平凡的微妙暗示,并经事实例证,在。NET数组有(故意)破碎的协方差。然而,常规的BCL在。NET Framework 4.0中已注明,以支持,而不必完全理解它的协变和逆变,因此开发人员将受益于该功能。

 

英文原文

C#, C++, and Delphi all have a generic type and method language feature. Although all three languages are statically typed, they implement generics in very different ways. I’m going to give a brief overview of the differences, both in terms of language features and implementation. I presume that Delphi Prism generics work essentially the same as C# generics, which, as you’ll see, is different than Delphi/Win32 generics.

Let me say at the outset that although all three systems work somewhat differently, I don’t see an overwhelming advantage to any one design. Generally, you can do what you need to do in all three environments. I’m writing this article not to claim that any one system is better than the others, but to point out some of the subtleties in the implementations.

Before I get started, I’d like to thank Barry Kelly for his useful feedback on my first draft of this article.

Compiling Instantiations

Every implementation of generic types works via a two-step process. First, you define a generic type or method with a "placeholder" for a specific type, which will be substituted later on. Later (exactly when depends upon the language), the type is "instantiated." Note that instantiating a generic type is very different from instantiating an object. The former happens within the compiler, whereas the latter happens at runtime.

Instantiation is triggered when some code uses a generic type or method with a specific type parameter, and means that based upon the generic definition and the types or values passed when the generic is used, a specific implementation is substituted in order to allow the generation of machine code. Instantiation is one of the most important differences between real generic types and using non-generic types with casts. In the end, different machine code is generated for instantiations for different type parameters.

In C# and Delphi, there is a language feature which is solely dedicated to implementing generic types and methods. In C++, on the other hand, the "templates" language feature can be used to implement generic types and methods, among many, many other things. It is even possible to do general-purpose programming using templates, which C++ programmers call "metaprogramming."

C++ templates require the template source code to be available when the code using the template is compiled. This is because the compiler does not actually compile the template as a separate entity, but rather instantiates it "in-place" and only compiles the instantiation. The C++ compiler is effectively doing code generation, substituting the type parameter (or value) for the placeholder for the type, and generating new code for the instantiation. Update: Moritz Beutel elaborates on this in his excellent comment on this post. You should read the full comment, but the short version is that the manner in which templates are compiled can result in errors in the code which uses the template appearing (from compiler error messages), incorrectly, to be errors in the template itself. Moreover, the implementation of most C++ compilers makes this problem even worse than what is necessary in order to implement the C++ standard.

In Delphi and C#, on the other hand, the generic type or method in the code which uses the generic type or method can be compiled separately. Therefore, you can compile a library which contains a generic type, and later on compile an executable which uses a instantiation of that type and has a reference to the binary library, rather than to the source code for the library.

Another way to think of this difference is that in C++, a template will not be compiled at all until it is used. In Delphi and C#, on the other hand, a generic type or method must be compiled before it can be used.

In Delphi, the compiler uses a feature closely related to the method inlining feature. This causes the compiler to store the relevant bits of the abstract syntax tree for the generic type parameter in the compiled DCU. When the code which uses the generic type is compiled, this bit of the abstract syntax tree is read and included in the abstract syntax tree for the code which uses the generic type, so that when machine code is produced, based on the new, “compound” abstract syntax tree, it looks, to the code emitter, like the type was defined with the type parameter "hard coded." Instead of linking to compiled code in the DCU, the code which uses the generic type emits new code for the instantiation into its own DCU.

Because generic instantiation is performed in the same area of the Delphi compiler which does method inlining, there are some limitations on what you can do in a generic method, or a method of a generic type. Like inlined methods, these methods cannot contain ASM. Also, calls to these methods cannot be inlined. These restrictions are limitations of the implementation, not of the language design, and could theoretically be removed in a future version of the compiler.

C# generics use the .NET Framework 2.0+, which has native support for generic types. The C# compiler emits IL which specifies that a generic type should be used, with certain type parameters. The .NET framework implements these types using one instantiation for any reference type, and custom instantiations for value types. (Don’t confuse “instantiation” with “instance” in the preceding sentence; they mean entirely different things in this context. There are usually many instances of one instantiation.) This is because a reference to a reference type is always the same size, whereas value types can be many different sizes. Later, the IL will be JITted into machine code, and, as with compiled C++ or Delphi code, types don’t really exist at the machine code level. In .NET, generic type instantiation and JITting are two distinct operations.

So one important difference in generics implementations is when the instantiation occurs. It occurs very early in C++ compilation, somewhat later for Delphi compilation, and as late as possible for .NET compilation.

Custom Specializations

Another very important difference is that C++ allows custom instantiations, called specializations, including specializations by value. With C# and Delphi, on the other hand, the only way to instantiate a generic type is to use that type with an explicit type parameter. The implementation will always be the same, with the exception of the type of the type parameter. Because C++ allows custom instantiations, it is easy for a programmer to write different implementations of a method, for example, for different integer values. Like operator overloading, this is a powerful feature which requires considerable self-restraint to avoid abuse.

Constraints

Delphi and C# both have a generic constraint feature, which allows/requires the developer of a generic or method type to limit which type parameter values can be passed. For example, a generic type which needs to iterate over some list of data could require that the type parameter support IEnumerable, in either language. This allows the developer of the generic type to make her intentions for the use of the type very clear. It also allows the IDE to provide code completion/IntelliSense on the type parameter, within the definition of the generic type. Also, it allows a user of the generic type to be confident that they are passing a legal value for the type parameter without having to compile their code to find out.

In C++, on the other hand, there is not presently any such feature. A more powerful/complex feature called "concepts" was considered for, but ultimately removed from, C++0x.

An implication of the lack of constraints is that C++ templates are duck typed. If a generic method calls some method, Foo on a type passed as the generic type parameter, then the template is going to compile just fine so long as the type parameter passed contains some method called Foo with the appropriate signature, no matter where or how it is defined.

Covariance and Contravariance

Let’s say I have a function which takes an argument of type IEnumerable<TParent>. Can I pass an argument of type IEnumerable<TChild>; to that function? What if the argument type were List<TParent>; instead of IEnumerable<TParent>? Or what if the generic type was the function result rather than the function argument? The formal names for these problems are covariance and contravariance. The precise details are too complicated to explain in this article, but the examples above summarize the most common times you run into the problem.

Delphi generics and C++ templates do not support covariance and contravariance. So the answers to the questions above are no, no, and no, although there are, of course, workarounds, like copying the data into a new list. In C# 4.0, function arguments and results can be declared covariant or contravariant, so the examples above can be made to work where appropriate. "Where appropriate" involves non-trivial subtleties hinted at above, and exemplified by the fact that arrays in .NET have (intentionally) broken covariance. However, the BCL routines in the .NET Framework 4.0 have been annotated to support covariance and contravariance when appropriate, so developers will benefit from the feature without having to fully understand it.

 

转载于:https://www.cnblogs.com/wxy8/archive/2011/01/17/1937085.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值