如何将我的思想从C ++迁移到C#


12

我是一位经验丰富的C ++开发人员,我对语言非常了解,并且已经大量使用了其某些特定功能。另外,我了解OOD的原理和设计模式。我现在正在学习C#,但是我无法停止无法摆脱C ++心态的感觉。我将自己与C ++的优势紧密联系在一起,以致于我无法缺少某些功能。而且我在C#中找不到任何好的解决方法或替代方法。

有什么好的做法设计模式成语是在C#不同于C ++的角度可以建议?如何获得一个完美的C ++设计,而不是C#中的笨拙?

具体来说,我找不到一种很好的C#方式来处理(最近的示例):

  • 控制需要确定性清除的资源(例如文件)的生命周期。这很容易using掌握,但是当资源的所有权正在转移时[...在线程之间]如何正确使用它呢?在C ++中,我只使用共享指针,并让它在适当的时候处理“垃圾回收”。
  • 不断为特定泛型的重写功能而苦苦挣扎(我喜欢C ++中的部分模板专门化之类的东西)。我应该放弃使用C#进行通用编程的任何尝试吗?也许泛型是有目的的,除了特定领域的问题外,使用C并不是C#风格?
  • 类似宏的功能。虽然通常是个坏主意,但对于某些问题领域,没有其他解决方法(例如,对语句的条件评估,例如只应运往Debug版本的日志)。没有它们意味着我需要放置更多if (condition) {...}样板,并且在触发副作用方面仍然不尽相同。

#1对我来说很难,我想知道答案。#2-您能举一个简单的C ++代码示例并解释为什么需要它吗?对于#3- C#用于生成其他代码。您可以读取一个CSV或一个XML或一个文件作为输入并生成一个C#或一个SQL文件。这比使用功能宏更强大。
工作

关于类宏功能,您想做什么特定的事情?我发现,通常有一种C#语言构造来处理我在C ++中使用宏所做的事情。另请查看C#预处理程序指令:msdn.microsoft.com/zh-cn/library/4y6tbswk.aspx
ravibhagw 2015年

在C ++中用宏完成的许多事情都可以在C#中通过反射完成。
塞巴斯蒂安·雷德尔

当有人说“正确的工具来完成正确的工作-根据您的需要选择一种语言”时,这是我的不满。对于使用通用语言的大型程序,这是无用的,无益的声明。也就是说,C ++比几乎任何其他语言都擅长的事情是确定性资源管理。确定性资源管理是程序的主要功能还是您习惯的功能?如果这不是重要的用户功能,则您可能对此过于关注。
2015年

Answers:


16

根据我的经验,没有书可以做到这一点。论坛提供了成语和最佳实践的帮助,但最终您将不得不花时间。通过解决C#中的许多不同问题进行练习。看看什么是困难/尴尬的,并尝试使它们下一次变得更少困难和更少尴尬。对我来说,这个过程花了大约一个月的时间才被我“理解”。

需要重点关注的最大问题是缺少内存管理。在C ++中,这支配着您做出的每一个设计决策,但在C#中却适得其反。在C#中,您通常想忽略对象的死亡或其他状态转换。这种关联增加了不必要的耦合。

通常,专注于最有效地使用新工具,而不是被旧工具的附件所打败。

如何获得一个完美的C ++设计,而不是C#中的笨拙?

通过意识到不同的习惯用法推动了不同的设计方法。您不能只是在大多数语言之间以1:1的比例移植某些东西,而在另一端期望一些美丽而惯用的东西。

但针对特定情况:

共享非托管资源 -这在C ++中是个坏主意,在C#中也是一个坏主意。如果您有一个文件,请打开它,使用它,然后关闭它。在线程之间共享它只是在问麻烦,并且如果只有一个线程真正在使用它,则让该线程打开/拥有/关闭它。如果由于某种原因您确实拥有一个长寿的文件,那么请创建一个类来表示该文件,并让应用程序根据需要进行管理(退出之前关闭,与应用程序关闭等事件联系在一起,等等)

部分模板专业化 -您在这里很不走运,尽管我使用C#的次数越多,我发现我在C ++中所做的模板使用就越多,属于YAGNI。当T 始终为int 时,为什么要使泛型?在C#中,最好通过接口的自由应用来解决其他情况。当接口或委托类型可以强烈键入要更改的内容时,则不需要泛型。

预处理程序有条件 -C#具有优势#if。更好的是,它具有条件属性,如果未定义编译器符号,则该条件属性将简单地从代码中删除该函数。


关于资源管理,在C#中,拥有管理器类(可以是静态的或动态的)是很常见的。
rwong

关于共享资源:在C#中,托管资源很容易(如果竞争条件无关紧要),非托管资源则更糟。
Deduplicator 2015年

@rwong-常见,但是经理类仍然是要避免的反模式。
Telastyn

关于内存管理,我发现切换到C#变得更加困难,尤其是在一开始的时候。我认为您必须比C ++更加了解。在C ++中,您确切地知道何时,何处分配,释放和引用每个对象。在C#中,这一切对您都是隐藏的。在C#中,很多时候您甚至没有意识到已经获得了对对象的引用,并且内存开始泄漏。追踪C#中的内存泄漏,这很有趣,而在C ++中通常很难弄清楚。当然,根据经验,您将学习C#的这些细微差别,因此它们往往不再是一个问题。
Dunk 2015年

4

据我所知,唯一的确定性生命周期管理方法是IDisposable,当您注意到需要转移此类资源的所有权时,它会崩溃(例如:无语言或类型系统支持)。

与您同在-C ++开发人员执行C#atm。-在C#类型/签名中缺乏对模型所有权转移(文件,数据库连接,...)的支持,这让我很烦,但是我不得不承认,“仅仅”依靠它确实可以正常工作有关约定和API文档的说明,请参见此处。

  • IDisposable做确定性清理IFF必要的。
  • 当您需要转让的所有权时IDisposable,只需确保所涉及的API对此明确,并且using在这种情况下不要使用,因为using可以这样说,这是无法“取消”的。

是的,它不如您在现代C ++的类型系统中所表达的优雅(移动语义)等,但是它已经完成了工作,而且(令我惊讶的是)整个C#社区似乎没有关心过多,AFAICT。


2

您提到了两个特定的C ++功能。我将讨论RAII:

由于C#是垃圾回收,因此通常不适用。最接近的C#结构可能是IDisposable接口,其用法如下:

using (FileStream fs = File.OpenRead(@"c:\path\filename.txt"))
{
   //Do stuff with the file.
} //File will be closed here.

您不应该期望IDisposable经常实施(这样做有些高级),因为对于托管资源而言这不是必需的。但是,对于任何实现,都应使用该using构造(或调用Dispose自己,如果using不可行)IDisposable。即使您搞砸了,精心设计的C#类也将尝试为您解决此问题(使用终结器)。但是,在垃圾收集语言中,RAII模式无法完全正常工作(因为收集是不确定的),因此您的资源清理将被延迟(有时是灾难性的)。


RAII确实是我最大的麻烦。假设我有一个线程创建并分配给另一个线程的资源(例如文件)。当该线程不再需要该线程时,如何确保它在某个特定时间被处置?当然,我可以调用Dispose,但是它看起来比C ++中的共享指针丑陋得多。里面没有RAII。
安德鲁

@Andrew您所描述的似乎暗示着所有权的转移,因此,第二个线程负责Disposing()文件,可能应该使用using
svick

@Andrew-通常,您不应该这样做。您应该改为告诉线程如何获取文件,并让它拥有生命周期的所有权。
Telastyn

1
@Telastyn是否意味着在C#中比在C ++中更大的问题是放弃对托管资源的所有权?非常令人惊讶。
安德鲁

6
@Andrew:让我总结一下:在C ++中,您可以对所有资源进行统一的半自动管理。在C#中,您可以完全自动地管理内存-以及完全手动管理其他所有内容。没有真正的改变,因此,您唯一的真正问题不是“如何解决此问题?”,而是“我如何习惯此问题?”。
杰里·科芬

2

这是一个非常好的问题,因为我已经看到许多C ++开发人员编写了糟糕的C#代码。

最好不要将C#视为“具有更好语法的C ++”。它们是不同的语言,在您的思维中需要不同的方法。C ++迫使您不断思考CPU和内存将要做什么。C#不是那样的。专门设计C#的目的是使您不必考虑CPU和内存,而可以考虑要编写业务领域

我看到的一个例子是,许多C ++开发人员都喜欢使用for循环,因为它们比foreach更快。这在C#中通常不是一个好主意,因为它限制了要迭代的集合的可能类型(因此限制了代码的可重用性和灵活性)。

我认为,从C ++调整为C#的最佳方法是尝试从不同的角度进行编码。刚开始这会很困难,因为多年来,您将完全习惯于使用大脑中的“ CPU和内存在做什么”线程来过滤所编写的代码。但是在C#中,您应该考虑业务域中对象之间的关系。“我想做什么”而不是“计算机在做什么”。

如果您要对对象列表中的所有内容执行某项操作,而不是在列表上编写for循环,请创建一个采用IEnumerable<MyObject>and并使用foreach循环的方法。

您的具体示例:

控制需要确定性清除的资源(例如文件)的生命周期。用手可以很容易地使用它,但是当资源所有权正在转移时如何正确使用它?在C ++中,我只使用共享指针,并让它在适当的时候处理“垃圾回收”。

绝对不要在C#中执行此操作(尤其是在线程之间)。如果您需要对文件进行某些操作,请一次在一个地方进行处理。可以编写一个包装类来管理在类之间传递的非托管资源,这是很好的做法(也是一种很好的做法),但是不要尝试在线程之间传递File并让单独的类来写/读/关闭/打开它。不要共享非托管资源的所有权。使用该Dispose模式进行清理。

不断为特定泛型的重写功能而苦苦挣扎(我喜欢C ++中的部分模板专门化之类的东西)。我应该放弃使用C#进行通用编程的任何尝试吗?也许泛型是有目的的,除了特定领域的问题外,使用C并不是C#风格?

C#中的泛型被设计为泛型。泛型的专业化应由派生类处理。List<T>如果为a List<int>或a,为什么行为应有所不同List<string>?上的所有操作List<T>都是通用的,因此适用于任何操作 List<T>。如果要更改上.Add方法的行为List<string>,请创建一个派生类MySpecializedStringCollection : List<string>或组合类MySpecializedStringCollection : IList<string>,该派生类或组合类在内部使用泛型,但以不同的方式进行操作。这可以帮助您避免违反《里斯科夫换人原则》,并避免对使用您的班级的其他人大加指责。

类似宏的功能。虽然通常是个坏主意,但对于某些问题领域,没有其他解决方法(例如,对语句的条件评估,例如只应运往Debug版本的日志)。没有它们意味着我需要放置更多if(condition)样板,并且在触发副作用方面仍然不相等。

正如其他人所说,您可以使用预处理程序命令来执行此操作。属性甚至更好。通常,属性是处理不是类的“核心”功能的事物的最佳方法。

简而言之,在编写C#时,请记住,您应该完全考虑业务领域,而不是CPU和内存。如有必要,您可以稍后进行优化,但是您的代码应反映您要映射的业务原则之间的关系。


3
WRT。资源转移:“您永远都不要这样做”不会减少恕我直言。通常,一个非常好的设计建议将一个IDisposable而不是原始资源)从一个类转移到另一个类:只要看一下StreamWriter API,这Dispose()对于传递的流就非常有意义。C#语言存在漏洞-您无法在类型系统中表达这一点。
马丁·巴

2
“我看到的一个例子是,许多C ++开发人员都喜欢使用for循环,因为它们比foreach更快。” 在C ++中这也是一个坏主意。零成本抽象是C ++的强大功能,在大多数情况下,foreach或其他算法不应比手写的for循环慢。
塞巴斯蒂安·雷德尔

1

对我而言,最不同的是对所有事物进行更新的趋势。如果您想要一个对象,则忘记尝试将其传递给例程并共享基础数据,似乎C#模式只是在为自己创建一个新对象。通常,这似乎更多地是C#方式。最初,这种模式效率很低,但是我想用C#创建大多数对象是一种快速的操作。

但是,还有一些静态类确实让我感到烦恼,因为您经常尝试创建一个静态类,却发现自己只能使用它。我发现使用它不太容易。

我很高兴在C#程序中在不再需要它时忽略它,但要记住该对象包含的内容-通常,您只需要考虑它是否包含一些“异常”数据,仅包含内存的对象就会只是自己走开,就必须处置其他人,或者您必须看看如何销毁它们-手动或如果没有,则使用using块。

您很快就会发现它,但是C#与VB几乎没有什么不同,因此您可以将其视为相同的RAD工具(如果它像VB.NET那样有助于C#的思考,那么这样做的语法只是一点点完全不同,而我过去使用VB的经历使我进入了RAD开发的正确思维定势)。唯一困难的是确定应该使用巨大的.net库中的哪一部分。Google绝对是您的朋友!


1
至少从语法角度来看,在C#中将对象传递给例程以共享基础数据是完全可以的。
布莱恩
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.