关于C#本质论和CLR via C#中译本,不吐不快

时间:2022-05-27 16:25:11

C#本质论和CLR via C#两本好书,周老师可能是俗务缠身,太忙了吧,翻译得只能让人呵呵了。

你要是忙,别接那么多活好不啦。

现在都在说供给侧改革,都在大力提倡工匠精神,我们做技术的,还是踏实点好,对不啦?

对照一下李建忠老师翻译的那一版CLR via C#,差距啊。

这里,仅把随手发现的几个问题记录一下,作为例子。

其实,在这两本中译本中,类似下面的翻译失当的例子随处可见。如果你不深究,也就无所谓了,但如果你是认认真真地在学习这两本书,这种无处不在的瑕疵,极度影响阅读感受,甚至影响对原作者思想的理解。

周老师这两本译作出版之前可能没有通读一遍进行校核,否则,这些不合逻辑的话怎么会没有发现并修正呢?

当我们学习李建忠老师翻译的那一版,就好像是同时在和两大高手学习技艺。读者不止从原作者那里学习到了知识,还从译者身上汲取很多营养,还能感受到译者的严谨求实。

而拜读周老师的译作,咋说呢?反正只读中译本是看不懂,需要随时参照原版去解读中译本。一本好书,从头到尾一口气读完的酣畅淋漓的感觉完全找不到。对译者失去了信任,读起来总是要加着小心,很不爽。

当然, 必须说,从周老师的译作中,我学习到了很多很多的知识。这里,只是希望周老师能把工作做得更好,贡献出更好的精品,以给我们这些C#的学习者更大的帮助。

C#本质论第四版

7.3.3 显式接口实现与隐式接口实现的比较

原文:

The key difference between implicit and explicit member interface implementation
lies not in the syntax of the method declaration, but rather in
the ability to access the method by name through an instance of the type
rather than via the interface.

原书中的译文:

对于隐式和显式实现的接口成员,关键区别不在于成员声明的语法,而在于通过类型的实例而不是接口访问成员的能力。

应该的意思是:

采用显式方式或者隐式方式来实现接口成员,其关键区别,并不在于声明接口成员所采用的语法的不同,而是:隐式方式实现的接口成员可由类的实例对象直接调用,而显式方式实现的接口,则必须首先将类的实例对象转换为接口类型后方可调用。

7.5 节,224页

原文:

In contrast, implementing ISettingsProvider assumes that there is
never a reason to have a class that can write settings without reading them.
The inheritance relationship between ISettingsProvider and IReadableSettingsProvider, therefore, forces the combined total of both interfaces on the ISettingsProvider class.

原书中的译文:

相反,实现ISettingsProvider是基于这样一个前提:任何类都不可能只能写入而不能读取设置。因此, ISettingsProvider、和IReadableSettingsProvider之间的继承关系强迫在FileSettingsProvider类上合并这两个接口。

合并两个接口,呵呵,请问周老师,如何合并呢?

应该是:ISettingsProvider的设计者之所以采用继承于IReadableSettingProvider,而不是将两个接口相互独立,并列提供给使用者,是基于这样一个考虑:任何类都不可能只能写入而不能
读取设置。采用继承的方式,则迫使使用者必须同时实现这两个接口(一个写入设置,一个读出设置)。而如果采用两接口相互独立地并列提供给用户的方式,则可能会导致用户只实现写入接口,而不实现读取接口。这是不符合常规的。

227页,关于利用接口实现多继承

原文:

One possible improvement that works if the implemented members are methods (not properties) is to define interface extension methods for the additional functionality “derived” from the second base class. An extension method on IPerson could provide a method called VerifyCredentials(), for example, and all classes that implement IPerson—even an IPerson interface that had no members but just extension methods—would have a default implementation of VerifyCredentials(). What makes this approach viable is the fact that polymorphism is still available, as is overriding. Overriding is supported because any instance implementation of a method will take priority over an extension method with the equivalent static signature.

原书译文:

如果被实现的成员是方法( 而非属性) ,那么有一个办法可对此进行改进。具体就是 为从第二个基类"派生"的附加功能定义接口扩展方法。例如, 可为IPeson定义扩展方法 VerifyCredentials() 。这样,实现IPerson ( 即使IPerson接口没有成员,只有扩展方法) 的所有类都会有verifyCdentials()的默认实现。这之所以可行,完全是多态性和重写的功劳。之所以支持重写, 是因为方法的任何实例实现都要优先于具有相同静态签名的扩展方法。

我不是很清楚原书中论述的关于多态的思想和技术,但是,我感觉后面加黑体的这句话应该这么翻译才对:

这之所以可行,是因为这样的事实:当overriding时,多态机制依然有效。因为方法的实例实现(就是在类中定义的实现)的优先级是高于具有相同静态签名的扩展方法的实现的。

感觉原文作者是这个意思吧:当你以实例方法的形式重写与接口方法静态签名相同的扩展方法后,由于实例方法优先级高,所以在使用过程中,实例方法会被优先调用,这也就实现了多态。因此,采用接口扩展方法实现多继承的方案是可行的。因为,当采用多基类继承方式的时候,是完美支持多态的,如果以接口扩展方法的方式实现多继承功能时不能完美支持多态,则该方式是不可行的。

309页 小标题 11.2.2 简单泛型类的定义

原文标题:Defining a Simple Generic Class

原作者的意思应该是“定义一个简单的泛型类”。这一小节实际上是以一个非常简单的泛型类为例,让读者对泛型类的定义有一个简单的了解而已。翻译成简单泛型类的定义,反正我的第一感觉是有那么一个泛型类,叫做简单泛型类,然后,我要去找复杂泛型类了。

311页 关于泛型参数

原文:

Note that it is legal, and indeed common, for the type argument for one generic type to be a type parameter of another

原书译文:

注意,一个泛型的类型实参可以成为另一个泛型类型的类型参数。

这句翻译只能是译者本人清楚这句话想表达的意思,读者是看不懂的。

原文是说,一个泛型类的类型参数可以作为另一个泛型类的类型参数。

翻译文本中,前后定语不一致,前面是泛型的,后面是泛型类型的,这看起来就像是故意把读者绕进去。

 320页 关于泛型的接口约束

原文:

When calling a method on a value typed as a generic type parameter, the compiler checks whether the method matches any method on any of the interfaces declared as constraints.

原书译文:

如果传递的类型实参是值类型,那么在调用它的方法时,编译器会检查方法是否与在约束中指定的任何接口的任何方法相匹配。

这翻译我也是醉了,能严谨一点不?翻译出来的东西,起码自己读得懂吧?

值类型 ,应该是Value type,这里作者是value typed,断句啊。原文省略了两个单词而已,应该是:When calling a method on a value which is typed as a generic type parameter, the compiler checks whether the method matches any method on any of the interfaces declared as constraints. 这里的value,应该是variable的意思,就是一个变量,一个值。

原文应该的意思是:当作为泛型类型传递进来的变量调用某一方法时,编译器会检查这个方法与接口约束中指定的那些接口所定义的各个方法是否有匹配。这句的翻译还要参考上下文的。原书上文中说,在泛型方法中调用接口的方法,不需要将泛型变量转换为接口类型,即使被调用的方法是显式实现的。(这在其他语境中是不可以的)。所以,这句话只是对“不需要将泛型变量转换为接口类型”这件事做一个解释而已,解释一下为什么不需要进行转换。

331页 关于协变与逆变

原文:

An IPair<PdaItem> can contain an address, but the object is really a Pair<Contact> that can contain only contacts, not addresses. Type safety is completely violated if unrestricted generic covariance is allowed.

与上述原文相关的一段源代码:

Contact contact1 = new Contact("Princess Buttercup"),
Contact contact2 = new Contact("Inigo Montoya");
Pair<Contact> contacts = new Pair<Contact>(contact1, contact2);
// This gives an error: Cannot convert type ...
// But suppose it did not.
// IPair<PdaItem> pdaPair = (IPair<PdaItem>) contacts;
// This is perfectly legal, but not type-safe.
// pdaPair.First = new Address("123 Sesame Street");

原书译文:

IPair<Pdaltem> 中可以包含地址,但Pair <Contact>对象只能包含联系人,不能包含地址。若允许不受限制的泛型协变性,类型安全将完全失去保障。

这翻译的,估计译者都不知道自己在说啥。

这句话,确实较难理解。特别是结合全书的上下文。这其实也有原作者的不严谨导致了出现问题。

在整本书中,一直采用PdaItem类、Contact类以及其它几个类做例子来讲解各种概念。(其实,个人感觉,这几个类本身关系就很牵强,并不适合做例子讲解各种基本概念)。

其中,Contact类中有一个字段是address(地址),而PdaItem类中,是没有这个字段的。

但是,综合上下文,这句中的address(原书错误,应该大写首字母,是Address),其实是指名为Address的一个类,且这个类继承于PdaItem。但是,原书中并未交代,样例代码也未说明。这就导致译者和其它原文读者的困惑。

明白了这一点,原文就容易理解了。

原文的意思应该是:IPair<PdaItem>中,可以容纳Address对象(因为Address从PdaItem派生),但是,IPair<PdaItem> pdaPair本身是Pair<Contact>对象,只能容纳Contact对象,而无法容纳Address对象。这就直接破坏了类型安全。

原书有错误,并不是你也跟着犯错的合理理由。否则岂不以讹传讹了。

其实道理很简单,就是List<String>类型的变量,不能赋值给List<Object>类型的变量listObj。因为,listObj实际上是List<String>类型,但从字面上看,其却是List<Object>类型。若哪一个用户将一个int变量放进来,岂不是会发生问题?这就导致类型不安全的问题发生。

还是331页 关于协变与逆变

原文:

Now it should also be clear why a list of strings may not be used as a list of objects. You cannot insert an integer into a list of strings, but you can insert an integer into a list of objects; thus it must be illegal to cast a list of strings to a list of objects, an error the compiler can enforce.

原书译文:

现在应该很清楚为什么字符串列表不能作为对象列表使用了。在字符串列表中不能插入整数,但在对象列表中可以插入整数, 所以从字符串列表转型成对象列表一定要被视为非法,使编译器能预防错误。

这翻译实在是不走心。

实际上,该段的上文中举了个例子,说List<string>不能赋值给List<object>。所以,这里的list of objects,实际上是List<object>,周老师你怎么能直接翻译成对象列表了?啥是对象列表?真是无语了。麻烦你在下一版中把对象列表替换为List<object>、捎带着把字符串列表替换为List<string>。

插入一点《CLR Via C# 第三版》的内容

中文版93页

英文原文:

All but the simplest of methods contain some prologue code, which initializes a method before it can start doing its work.

原书译文:

在一个最基本的方法中,应包含一些“序幕”代码。。。。

这其实是基本的英文语法,应该是:除了最简单的方法之外,所有的方法都应该包含一些“序言代码”(我不知道是不是应该这么翻译,其实就是一些初始化代码,用于完成声明并初始化一些局部变量等工作,跟随其后的,是方法的“正文”,也就是实现方法功能的“正式”代码)

最简单的方法,其实是不需要这些“序幕”代码的。但是原书译文,实在是让人费解,误导,耽误读者时间去思量这到底是啥意思。译者周老师如果稍微严肃一点,这样的错误是完全可以避免的。

其实,CLR Via C#的作者Jeffery是语言大师,遣词造句非常规范,说的都是非常正宗的英语,浅显易懂,条理清晰。Jeffery还是说故事的大师,看他举的例子,可比C#本质论书中的例子贴切得多。Jeffery还是探究问题究竟的大师,凡事问个底儿掉,我们就喜欢这样的人,只有这样的人,才能让我们透过现象看到本质,才能掌握代码后面的底层的东西。

不过,C#本质论自有它的用处,个人感觉,这本书更适合作为一个参考手册,可以随时查阅一些你需要的规则。

继续吐槽C#本质论

中文版339页,委托和lambda表达式,序言部分

英文原文:For example, if you pass to a method a reference to IComparer<int>, odds are good that the called method will itself call the Compare() method on the object you provided. In this case, the interface is nothing more than a way to pass a reference to a single method that can be invoked.

原书译文:例如, 向方法传递一个ICompa rer <int >引用,被调用的方法本身可以在提供的对象上调用Compare()方法。在这种情况下, 接口的作用只是向最终被调用的方法传递一个引用。

简直无力吐槽了都。

我直接翻译一下吧:例如,如果你将一个IComparer<int>引用对象传递给一个方法(假如这个方法是A吧),那么很有可能这个被调用的方法A会利用你传递进来的引用对象调用Compare()方法(Compare()方法是IComparer接口定义的一个方法)。

关键是后面一句的翻译:在这种情况下,接口的作用莫过于提供了传递一个可以被调用的单一方法的引用的方式。

原文中pass a reference to a single method,是说这个引用是指向一个单一方法的,而不是将一个引用传递给单一方法。

虽然我的翻译很绕口,但是起码意思是对的,你费点劲,仔细读一下,还是能知道原作者想说什么。而周老师的译文,我相信他自己都不知道自己在说什么。

这一段是原文作者讲述委托这一章内容之前的一个引子,目的就是引出委托来。译者翻译的时候,应该结合上下文,认真领会原作者意图。不要随手就来,不瞻前不顾后,这样翻出来的文字是没有意义的,不知让读者看不懂,还更有可能误导读者,这样真的不好。

中文版 341页 关于委托的数据类型

英文原文:To pass a method as an argument, a data type is required to represent that method; this data type is called a delegate because it “delegates” the call to the method referred to by the object.

原书译文:为了能将方法作为参数传递,必须要有一个能够表示方法的数据类型。这个数据类型就是委托,因为它"委托"调用对象所引用的方法。

delegate,在C#中,大家都翻译为“委托”。但是delegate翻译成中文,有委托、代理的意思。上面原文中引号里面的delegates就是个动词,应该翻译成“代理”才合适。

中文版 344页 关于委托的实例化

英文原文:

The conversion from the method group—the expression that names the method—to the delegate type automatically creates a new delegate object in C# 2.0 and later。

原书翻译:

从C# 2.0开始,从方法组(为方法命名的表达式)向委托类型的转换会自动创建一个新的委托对象。

请周老师讲一讲括号里面“为方法命名的表达式”是啥意思?

中文版394页 关于泛型集合

英文原文:

Collecting one particular type of object within a collection is a key characteristic of a generic collection.

原书翻译:

泛型集合的一个关键特征就是将一种特定类型的对象全都收集到一个集合中。

正确的意思应该是:泛型集合的一个关键特征是集合中存储的都是某一特定类型的对象。

将一种特定类型的对象全部收集到一个集合中,是不可能完成的任务。即使你将这种类型的对象全部收集到集合中了,你并没有阻止其他类型的变量进入集合,这不是作者的原意。

中文版  428页 关于linq

英文原文:In this example, the type FileInfo was chosen because it has the two relevant fields needed for the desired output: the filename and the last write time. There might not be such a convenient type if you needed other information not captured in the FileInfo object.

原书译文:注意在这个例子中,选择Filelnfo是因为它恰好有输出所需的两个字段, 文件名和上一次写入时间。如果需要Filelnfo对象没有捕捉到的信息,那么或许没有现成类型可供使用。

原书作者说这句话是为了讲解匿名类型在Linq的select语句中的应用。select语句将from后面的迭代变量(或者叫范围变量)映射到(周老师翻译为投射)另外的类型。这段话的上面,作者举了个例子,将文件名映射到FileInfo类型。

作者这句话的意思应该是:在这个例子中之所以选择FileInfo这个类型,是因为这个类正好有两个适合于被输出的字段,一个是文件名,另一个是最后写入时间。但是,当你需要输出FileInfo类型中没有包含的(注释:不是周老师原文翻译的捕获到的信息)其它信息的时候,可能没有现成的合适类型供我们使用。

这样,后面作者就顺理成章地开始将匿名类型在select后面的作用了。

中文版445页 关于IComparer<T>和IComparable<T>

英文原文:

The difference between IComparable<T> and IComparer<T> is subtle but important. The first interface means, “I know how to compare myself to another instance of my type.” The latter means, “I know how to compare two things of a given type.”

原书翻译:

IComparable<T>和IComparer< T >的区别很细微,但却很重要。前者说"我知道如何将我自己和我的类型的另一个实例进行比较" ,后者说"我知道如何比较给定类型的两个实例" 。

真是用上了脚后跟也看不懂周老师想表达什么意思。

其实,很多人对这两个接口也是糊涂的。MSDN也不说清楚两者的关系与区别,网上查了一些帖子,无论中英文网站,大多也都说的模棱两可。

个人理解:

其实这个问题很简单。C#本质论作者的上面这句话已经交代得非常清楚了:二者的区别很细微但很重要。第一个接口IComparable<T>是说,我知道如何将自己和同类型的另一个实例进行比较。后一个接口IComparer<T>是说:我知道如何比较给定类型中的两个“东西”。

嘿嘿,啥是给定类型中的两个东西?就像书中紧跟着给出的例子,联系人类型。这个类型实际上有很多字段可以用于比较和排序,譬如名字、地域、年龄等等。与此相比较,一个字符串类型则简单得多,主要排序方案就那么一种:对字符串本身进行升序或降序排列。因此,对于联系人这样的类型,不应该实现一个通用的比较方法,而应该针对需要排序的字段,分别设计比较的方法。

归纳一下:

IComparable<T>可用于类似于字符串类型这样的类型,该类型本身有明确的排序意义,用一个比较方法即可实现类型对象的比较;

IComparer<T>则应该用于类似于联系人这样的类型,一般说来,该类型本身并没有明确的排序意义,排序只能以类型中的某个字段为依据进行。

吐槽就到这里吧。