思考をC ++からC#に移行する方法


12

私は経験豊富なC ++開発者であり、言語を非常に詳細に知っており、その特定の機能の一部を集中的に使用しました。また、私はOODの原則とデザインパターンを知っています。私は今C#を学んでいますが、C ++の考え方を取り除くことができないという気持ちを止めることはできません。私はC ++の強みに一生懸命縛り付けたので、いくつかの機能がなければ生きられません。また、C#でそれらの適切な回避策または代替策を見つけることができません。

どのような良いプラクティスデザインパターンイディオム C ++の観点から、C#で異なっているあなたは提案することができますか?C#で馬鹿げていない完璧なC ++デザインを取得するにはどうすればよいですか?

具体的には、対処するC#風の良い方法を見つけることができません(最近の例):

  • 確定的なクリーンアップを必要とするリソース(ファイルなど)の有効期間を制御します。これは簡単usingに手に入れることができますが、リソースの所有権が転送されているときに適切に使用する方法[... betwen threads]?C ++では、単に共有ポインタを使用して、適切なタイミングで「ガベージコレクション」を処理します。
  • 特定のジェネリックの関数をオーバーライドすることに常に苦労しています(C ++での部分的なテンプレートの特殊化などが大好きです)。C#でジェネリックプログラミングを行う試みを放棄する必要がありますか?ジェネリックは意図的に制限されている可能性があり、特定の問題領域を除いてジェネリックを使用するのはC#っぽくないですか?
  • マクロのような機能。一般に悪い考えですが、問題の一部の領域では、他の回避策はありません(たとえば、デバッグリリースにのみ送信されるログのように、ステートメントの条件付き評価)。それらを持たないということは、if (condition) {...}定型文を追加する必要があることを意味しますが、副作用を引き起こすという点ではまだ同じではありません。

#1は私にとって難しいものです。自分で答えを知りたいです。#2-簡単なC ++コード例を挙げて、なぜそれが必要なのか説明してもらえますか?#3- C#他のコードを生成するために使用します。あなたは読むことができるCSVか、XMLまたはあなたが入力として何を提出しており、生成C#またはSQLファイルを。これは、機能マクロを使用するよりも強力です。
ジョブ

マクロのような機能については、具体的にどのようなことをしようとしていますか?私が見つけたのは、通常、C ++でマクロを使用して実行したことを処理するためのC#言語構造があることです。C#プリプロセッサディレクティブも確認してください
。msdn.microsoft.com/ en

C ++でマクロを使用して行われた多くのことは、C#でリフレクションを使用して行うことができます。
セバスチャンレッド

誰かが「適切な仕事に適切なツール-あなたが必要としているものに基づいて言語を選択してください」と言うとき、それは私のペットのぞき見の1つです。汎用言語の大規模プログラムの場合、これは役に立たない、役に立たないステートメントです。そうは言っても、C ++が他のほとんどの言語よりも優れているのは、確定的なリソース管理です。決定論的なリソース管理はプログラムの主要な機能ですか、それとも慣れているものですか?それが重要なユーザー機能でない場合は、それに過度に集中している可能性があります。
ベン

回答:


16

私の経験では、これを行う本はありません。フォーラムはイディオムやベストプラクティスを支援しますが、最終的には時間をかける必要があります。C#でさまざまな問題を解決して練習します。難しい/厄介なものを見て、次回はそれらをより難しくなく、より厄介にしないようにしてください。私にとって、このプロセスは、「それを手に入れた」前に約1か月かかりました。

注目すべき最大のことは、メモリ管理の欠如です。C ++では、これはあなたが行うすべての設計上の決定を支配しますが、C#では逆効果です。C#では、オブジェクトの死亡またはその他の状態遷移を無視することがよくあります。このような関連付けにより、不要な結合が追加されます。

ほとんどの場合、新しいツールを最も効果的に使用することに焦点を合わせ、古いツールへのアタッチメントに打ち勝つことはしません。

C#で馬鹿げていない完璧なC ++デザインを取得するにはどうすればよいですか?

異なるイディオムが異なる設計アプローチに向かっていることを理解することにより。(ほとんどの)言語間で何かを1対1で移植して、もう一方の端に美しくて慣用的なものを期待することはできません。

しかし、特定のシナリオに応じて:

共有管理されていないリソース -これはC ++では悪い考えであり、C#では同じくらい悪い考えです。ファイルがある場合は、それを開いて使用し、閉じます。スレッド間で共有することは、単に問題を求めているだけであり、1つのスレッドのみがそれを実際に使用している場合は、そのスレッドをオープン/所有/クローズします。何らかの理由で実際に長寿命のファイルがある場合は、それを表すクラスを作成し、必要に応じてアプリケーションに管理させます(終了前に閉じる、アプリケーション終了イベントに近づけるなど)

部分テンプレート専門化 -ここでは運が悪いのですが、C#を使用すればするほど、C ++でYAGNIに分類されるテンプレートの使用が増えます。Tが常に intであるのに、なぜ一般的なものを作成するのですか?他のシナリオは、インターフェイスのリベラルなアプリケーションによってC#で最もよく解決されます。インターフェースまたはデリゲート型が、変更したいものを強く入力できる場合、ジェネリックは必要ありません。

プリプロセッサの条件 -C#には#ifあります。さらに良いことに、コンパイラシンボルが定義されていない場合、その関数をコードから単純に削除する条件付き属性があります。


リソース管理に関して、C#では、マネージャークラス(静的または動的)を持つことが非常に一般的です。
rwong

共有リソースについて:管理されているリソースはC#では簡単です(競合状態が関係ない場合)、管理されていないリソースははるかに厄介です。
デデュプリケーター

@rwong-一般的ですが、マネージャークラスは依然として回避すべきアンチパターンです。
テラスティン

メモリ管理に関しては、C#に切り替えると、特に最初はこれがより困難になることがわかりました。C ++よりも注意する必要があると思います。C ++では、すべてのオブジェクトがいつ、どこで割り当てられ、割り当て解除され、参照されているかが正確にわかります。C#では、これはすべてあなたから隠されています。多くの場合、C#では、オブジェクトへの参照が取得され、メモリリークが発生していることに気づきさえしません。C#でのメモリリークの追跡をお楽しみください。これは通常、C ++では簡単に把握できます。もちろん、経験があれば、C#のこれらのニュアンスを学習できるため、問題が少なくなる傾向があります。
ダンク

4

私が見る限り、決定論的なライフタイム管理を可能にするのは IDisposable、あなたが気づいたように、そのようなリソースの所有権を譲渡する必要があるときに故障します(言語やタイプシステムのサポートなしなど)。

あなたと同じ船に乗っている-C#atmを行うC ++開発者。-C#タイプ/署名の所有権移転(ファイル、db-connections、...)をモデル化するためのサポートの欠如は私にとって非常に迷惑でしたが、「ちょうど」依存してもまったく問題なく動作することを認めなければなりませんこれを正しく行うための規約とAPIドキュメントについて。

  • IDisposable決定論的なクリーンアップを行うために使用iff必要な。
  • の所有権を譲渡する必要がある場合は、IDisposable関連するAPIが明確になっていることを確認し、usingこの場合は使用しないでくださいusing。いわば「キャンセル」できないためです。

はい、それは現代のC ++の型システムで表現できるもの(セマティックスの移動など)ほどエレガントではありませんが、仕事はやり遂げられ、C#コミュニティ全体はそうではありません気をつけて、AFAICT。


2

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あなたが説明するものは所有権の移転を暗示しているように見えるので、今では2番目のスレッドがDisposing()ファイルを担当し、おそらくを使用する必要がありますusing
svick

@Andrew-一般に、あなたはそうすべきではありません。代わりに、ファイルを取得する方法をスレッドに指示し、ファイルにライフタイムの所有権を持たせる必要があります。
テラスティン

1
@Telastyn管理されたリソースの所有権を譲渡することは、C ++よりもC#の方が大きな問題になるということですか?驚くべきことです。
アンドリュー

6
@Andrew:要約しましょう。C++では、すべてのリソースを均一に半自動で管理できます。C#では、メモリを完全に自動管理し、他のすべてを完全に手動で管理できます。実際に変更することはないので、あなたの唯一の本当の質問は、「これをどのように修正しますか?」ではなく、「これにどのように慣れますか?」です。
ジェリーコフィン

2

多くのC ++開発者がひどいC#コードを書いているのを見てきたので、これは非常に良い質問です。

C#を「より良い構文を備えたC ++」と考えないことが最善です。彼らはあなたの思考で異なるアプローチを必要とする異なる言語です。C ++を使用すると、CPUとメモリが何をするのかを常に考えるようになります。C#はそうではありません。C#は、特にCPUとメモリについて考えるのではなく、作成するビジネスドメインについて考えるように設計されています。

私が見たこの一例は、多くのC ++開発者がforeachよりも高速であるため、forループの使用を好むことです。これは通常、C#では不適切なアイデアです。反復されるコレクションの種類(およびコードの再利用性と柔軟性)が制限されるためです。

C ++からC#に再調整する最善の方法は、異なる視点からコーディングを試みることです。最初は、これは難しいでしょう。なぜなら、長年にわたって、あなたの脳で「CPUとメモリが何をしているのか」スレッドを使用して、書いたコードをフィルタリングすることに完全に慣れるからです。ただし、C#では、代わりにビジネスドメイン内のオブジェクト間の関係について考える必要があります。「コンピューターは何をしているのか」とは対照的に「何をしたいのか」。

リストにforループを書く代わりに、オブジェクトのリスト内のすべてに何かをしたい場合は、を取り、IEnumerable<MyObject>使用するメソッドを作成しますforeachます。

具体的な例:

確定的なクリーンアップを必要とするリソース(ファイルなど)の有効期間を制御します。これは手元で簡単に使用できますが、リソースの所有権を転送するときに適切に使用するにはどうすればよいでしょうか(...スレッド間)?C ++では、単に共有ポインタを使用して、適切なタイミングで「ガベージコレクション」を処理します。

これをC#で(特にスレッド間で)実行しないでください。ファイルに対して何かを行う必要がある場合は、一度に1か所で行ってください。クラス間で渡されるアンマネージリソースを管理するラッパークラスを作成することは問題ありません(実際には良い習慣です)。管理されていないリソースの所有権を共有しないでください。Disposeパターンを使用して、クリーンアップを処理します。

特定のジェネリックの関数をオーバーライドすることに常に苦労しています(C ++での部分的なテンプレートの特殊化などが大好きです)。C#でジェネリックプログラミングを行う試みを放棄する必要がありますか?ジェネリックは意図的に制限されている可能性があり、特定の問題領域を除いてジェネリックを使用するのはC#っぽくないですか?

C#のジェネリックは、ジェネリックになるように設計されています。ジェネリックの特殊化は、派生クラスで処理する必要があります。List<T>それがa List<int>またはaである場合、なぜ異なる振る舞いをするのList<string>ですか?上の操作はすべてList<T>に適用するなどの一般的なので、ある任意の List<T>。aの.Addメソッドの動作を変更する場合は、内部でジェネリックを使用しますが、異なる方法で処理List<string>を行う派生クラスMySpecializedStringCollection : List<string>または合成クラスMySpecializedStringCollection : IList<string>を作成します。これにより、リスコフの代替原則に違反したり、クラスを使用する他のユーザーを過度にねじ込んだりすることを回避できます。

マクロのような機能。一般に悪い考えですが、問題の一部の領域では、他の回避策はありません(たとえば、デバッグリリースにのみ送信されるログのように、ステートメントの条件付き評価)。それらがないということは、if(condition){...}定型文を追加する必要があり、副作用を引き起こすという点ではまだ同じではないことを意味します。

他の人が言ったように、プリプロセッサコマンドを使用してこれを行うことができます。属性はさらに優れています。一般に、属性はクラスの「コア」機能ではないものを処理するための最良の方法です。

要するに、C#を書くときは、CPUとメモリではなく、ビジネスドメインのみを考えるべきだということに留意してください。必要に応じて後から最適化できますが、コードには、マップしようとしているビジネス原則間の関係を反映させる必要があります。


3
に関して。リソースの転送:「これを絶対にしないでください」と言っても、それはカットされません。多くの場合、非常に良いデザインは、の転送を示しているIDisposableいないだけ参照してください。別のクラスからの生のリソース)は、StreamWriterこれは完璧に理にかなっているAPI Dispose()渡されたストリームのを。そして、C#言語に穴があります-これを型システムで表現することはできません。
マーティンBa

2
「私が見たこの一例は、多くのC ++開発者がforeachよりも高速であるため、forループを使用することを好むということです。」これはC ++でも悪い考えです。ゼロコストの抽象化はC ++の強力な機能であり、foreachまたはその他のアルゴリズムは、ほとんどの場合、手書きのforループより遅くなるべきではありません。
セバスチャンレッド

1

私にとって最も違ったことは、すべてを新しくする傾向でした。オブジェクトが必要な場合、それをルーチンに渡して基になるデータを共有しようとするのを忘れて、C#パターンは自分で新しいオブジェクトを作成するだけのようです。これは、一般に、はるかにC#の方法のようです。最初はこのパターンは非常に効率が悪いように見えましたが、C#でほとんどのオブジェクトを作成するのは簡単な操作だと思います。

しかし、静的クラスもあります。これは、使用する必要があることを確認するためだけに作成しようとすることが多いためです。どちらを使用するかを知るのはそれほど簡単ではありません。

C#プログラムでは、不要になったときに無視するだけで非常に満足していますが、そのオブジェクトに何が含まれているかを覚えているだけです-一般に、「異常な」データがあるかどうかだけを考える必要があります。自分でやめるだけで、他の人は処分しなければならないか、それらを破壊する方法を調べる必要があります-手動で、またはそうでない場合はブロックを使用して。

ただし、C#はVBとほとんど変わらないので、同じRADツールとして扱うことができます(VB.NETのようにC#を考えるのに役立つ場合は、構文はごくわずかです異なっており、VBを使用していた私の昔は、RAD開発の正しい考え方になりました)。唯一難しいのは、使用する巨大な.netライブラリのビットを決定することです。Googleは間違いなくあなたの友達です!


1
C#では、少なくとも構文の観点から、基礎となるデータを共有するためにオブジェクトをルーチンに渡すことはまったく問題ありません。
ブライアン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.