バギーC#コード:C#プログラミングで最もよくある10の間違い
公開: 2022-03-11Cシャープについて
C#は、Microsoft共通言語ランタイム(CLR)を対象とするいくつかの言語の1つです。 CLRを対象とする言語は、言語間の統合と例外処理、強化されたセキュリティ、コンポーネントの相互作用の簡素化されたモデル、デバッグおよびプロファイリングサービスなどの機能の恩恵を受けます。 今日のCLR言語の中で、C#は、Windowsデスクトップ、モバイル、またはサーバー環境を対象とする複雑で専門的な開発プロジェクトに最も広く使用されています。
C#は、オブジェクト指向の強い型の言語です。 コンパイル時と実行時の両方でC#の厳密な型チェックを行うと、一般的なC#プログラミングエラーの大部分ができるだけ早く報告され、その場所が非常に正確に特定されます。 これにより、Cシャープのプログラミングで多くの時間を節約できます。これは、型安全性の適用により寛大な言語で問題のある操作が行われた後、ずっと後に発生する可能性のある不可解なエラーの原因を追跡する場合と比較されます。 ただし、多くのC#コーダーは、無意識のうちに(または不注意に)この検出の利点を捨ててしまい、このC#チュートリアルで説明されている問題のいくつかにつながります。
このCシャーププログラミングチュートリアルについて
このチュートリアルでは、C#プログラマーが犯した最も一般的な10のC#プログラミングの間違い、または回避すべき問題について説明し、支援を提供します。
この記事で説明する間違いのほとんどはC#固有のものですが、CLRを対象とする、またはFramework Class Library(FCL)を利用する他の言語にも関連するものもあります。
一般的なC#プログラミングの間違い#1:値のような参照の使用またはその逆
C ++や他の多くの言語のプログラマーは、変数に割り当てる値が単なる値なのか、既存のオブジェクトへの参照なのかを制御することに慣れています。 ただし、C Sharpプログラミングでは、その決定は、オブジェクトをインスタンス化して変数に割り当てるプログラマーではなく、オブジェクトを作成したプログラマーによって行われます。 これは、C#プログラミングを学ぼうとする人にとって一般的な「落とし穴」です。
使用しているオブジェクトが値型か参照型かわからない場合は、いくつかの驚きに遭遇する可能性があります。 例えば:
Point point1 = new Point(20, 30); Point point2 = point1; point2.X = 50; Console.WriteLine(point1.X); // 20 (does this surprise you?) Console.WriteLine(point2.X); // 50 Pen pen1 = new Pen(Color.Black); Pen pen2 = pen1; pen2.Color = Color.Blue; Console.WriteLine(pen1.Color); // Blue (or does this surprise you?) Console.WriteLine(pen2.Color); // Blue
ご覧のとおり、 Point
オブジェクトとPen
オブジェクトはどちらもまったく同じ方法で作成されましたが、新しいX
座標値がpoint1
に割り当てられたとき、 point2
の値は変更されませんでしたが、新しい色が割り当てられたとき、 pen1
の値は変更されました。 pen2
。 したがって、 point1とpoint1
にはそれぞれPoint
オブジェクトの独自のコピーが含まれているのに対し、 pen1
とpoint2
には同じPen
オブジェクトへの参照が含まれているとpen2
できます。 しかし、この実験を行わずに、どうすればそれを知ることができますか?
答えは、オブジェクトタイプの定義を確認することです(オブジェクトタイプの名前の上にカーソルを置き、F12キーを押すと、Visual Studioで簡単に実行できます)。
public struct Point { ... } // defines a “value” type public class Pen { ... } // defines a “reference” type
上記のように、C#プログラミングでは、 struct
キーワードを使用して値の型を定義し、 class
キーワードを使用して参照型を定義します。 C ++とC#のキーワードの多くの類似点によって誤った安心感に陥った、C ++のバックグラウンドを持つ人々にとって、この動作は、C#チュートリアルからの助けを求めるかもしれない驚きとして来る可能性があります。
オブジェクトをメソッドパラメータとして渡し、そのメソッドにオブジェクトの状態を変更させる機能など、値と参照型の間で異なる動作に依存する場合は、 C#プログラミングの問題を回避するための正しいタイプのオブジェクト。
一般的なC#プログラミングの間違い#2:初期化されていない変数のデフォルト値の誤解
C#では、値の型をnullにすることはできません。 定義上、値型には値があり、値型の初期化されていない変数でも値が必要です。 これは、そのタイプのデフォルト値と呼ばれます。 これにより、変数が初期化されていないかどうかを確認すると、通常は予期しない結果が発生します。
class Program { static Point point1; static Pen pen1; static void Main(string[] args) { Console.WriteLine(pen1 == null); // True Console.WriteLine(point1 == null); // False (huh?) } }
point1
がnullにならないのはなぜですか? 答えは、 Point
は値型であり、 Point
のデフォルト値は(0,0)であり、nullではないということです。 これを認識できないことは、C#で行う非常に簡単な(そして一般的な)間違いです。
多くの(すべてではありませんが)値タイプにはIsEmpty
プロパティがあり、デフォルト値と等しいかどうかを確認できます。
Console.WriteLine(point1.IsEmpty); // True
変数が初期化されているかどうかを確認するときは、そのタイプの初期化されていない変数がデフォルトでどの値を持つかを確認し、nullであることに依存しないでください。
一般的なC#プログラミングの間違い#3:不適切または不特定の文字列比較方法の使用
C#で文字列を比較する方法はたくさんあります。
多くのプログラマーは文字列の比較に==
演算子を使用しますが、これは実際には使用するのが最も望ましくない方法の1つです。これは、主に、どのタイプの比較が必要かをコードで明示的に指定しないためです。
むしろ、C#プログラミングで文字列の同等性をテストするための推奨される方法は、 Equals
メソッドを使用することです。
public bool Equals(string value); public bool Equals(string value, StringComparison comparisonType);
最初のメソッドシグネチャ(つまり、 comparisonType
パラメーターなし)は、実際には==
演算子を使用する場合と同じですが、文字列に明示的に適用されるという利点があります。 文字列の序数比較を実行します。これは基本的にバイトごとの比較です。 多くの場合、これはまさに必要なタイプの比較です。特に、ファイル名、環境変数、属性など、プログラムで値が設定されている文字列を比較する場合はそうです。これらの場合、通常の比較が実際に正しいタイプである限り、その状況でのcomparisonType
の場合、comparationTypeなしでEquals
メソッドを使用することの唯一の欠点は、コードを読んでいる誰かが、実行している比較のタイプを知らない可能性があることです。
ただし、文字列をcomparisonType
するたびにcompareationTypeを含むEquals
メソッドのシグネチャを使用すると、コードが明確になるだけでなく、どのタイプの比較を行う必要があるかを明示的に考えることができます。 英語は通常の比較と文化に敏感な比較の間にそれほど多くの違いを提供しないかもしれないが、他の言語は十分に提供し、他の言語の可能性を無視することはあなた自身に多くの可能性を開くので、これは行う価値のあることです将来のエラー。 例えば:
string s = "strasse"; // outputs False: Console.WriteLine(s == "straße"); Console.WriteLine(s.Equals("straße")); Console.WriteLine(s.Equals("straße", StringComparison.Ordinal)); Console.WriteLine(s.Equals("Straße", StringComparison.CurrentCulture)); Console.WriteLine(s.Equals("straße", StringComparison.OrdinalIgnoreCase)); // outputs True: Console.WriteLine(s.Equals("straße", StringComparison.CurrentCulture)); Console.WriteLine(s.Equals("Straße", StringComparison.CurrentCultureIgnoreCase));
最も安全な方法は、常にcomparisonType
パラメーターをEquals
メソッドに提供することです。 基本的なガイドラインは次のとおりです。
- ユーザーが入力した文字列、またはユーザーに表示される文字列を比較する場合は、カルチャに依存する比較(
CurrentCulture
またはCurrentCultureIgnoreCase
)を使用します。 - プログラム文字列を比較するときは、序数比較(
Ordinal
またはOrdinalIgnoreCase
)を使用します。 -
InvariantCulture
およびInvariantCultureIgnoreCase
は、通常の比較がより効率的であるため、非常に限られた状況を除いて、通常は使用されません。 カルチャを意識した比較が必要な場合は、通常、現在のカルチャまたは別の特定のカルチャに対して実行する必要があります。
Equals
メソッドに加えて、文字列はCompare
メソッドも提供します。このメソッドは、同等性のテストだけでなく、文字列の相対的な順序に関する情報を提供します。 この方法は、C#の問題を回避するために、上記と同じ理由で、 <
、 <=
、 >
、および>=
演算子よりも適しています。
一般的なC#プログラミングの間違い#4:コレクションを操作するための(宣言型ではなく)反復ステートメントの使用
C#3.0では、言語への統合言語クエリ(LINQ)の追加により、コレクションのクエリと操作の方法が一変しました。 それ以降、コレクションを操作するために反復ステートメントを使用している場合は、おそらく必要なときにLINQを使用しませんでした。
一部のC#プログラマーは、LINQの存在すら知りませんが、幸いなことに、その数はますます少なくなっています。 ただし、LINQキーワードとSQLステートメントは類似しているため、データベースにクエリを実行するコードでのみ使用されると多くの人が考えています。
データベースクエリはLINQステートメントの非常に一般的な使用法ですが、実際には、列挙可能なコレクション(つまり、IEnumerableインターフェイスを実装するオブジェクト)に対して機能します。 したがって、たとえば、アカウントの配列がある場合、C#リストforeachを作成する代わりに、次のようにします。
decimal total = 0; foreach (Account account in myAccounts) { if (account.Status == "active") { total += account.Balance; } }
あなたはただ書くことができます:
decimal total = (from account in myAccounts where account.Status == "active" select account.Balance).Sum();
これは、この一般的なC#プログラミングの問題を回避する方法の非常に単純な例ですが、単一のLINQステートメントでコード内の反復ループ(またはネストされたループ)内の数十のステートメントを簡単に置き換えることができる場合があります。 また、一般的なコードが少ないということは、バグが発生する機会が少ないことを意味します。 ただし、パフォーマンスの面でトレードオフが発生する可能性があることに注意してください。 パフォーマンスが重要なシナリオでは、特に反復コードがコレクションについてLINQでは不可能であると想定できる場合は、必ず2つの方法のパフォーマンスを比較してください。
一般的なC#プログラミングの間違い#5:LINQステートメントで基になるオブジェクトを考慮しない
LINQは、コレクションがメモリ内オブジェクト、データベーステーブル、またはXMLドキュメントであるかどうかに関係なく、コレクションを操作するタスクを抽象化するのに最適です。 完璧な世界では、基礎となるオブジェクトが何であるかを知る必要はありません。 しかし、ここでの誤りは、私たちが完璧な世界に住んでいると仮定していることです。 実際、同じLINQステートメントをまったく同じデータで実行すると、そのデータの形式が異なる場合、異なる結果が返される可能性があります。
たとえば、次のステートメントについて考えてみます。
decimal total = (from account in myAccounts where account.Status == "active" select account.Balance).Sum();
オブジェクトのaccount.Status
の1つが「アクティブ」(大文字のAに注意)に等しい場合はどうなりますか? myAccounts
がDbSet
オブジェクト(デフォルトの大文字と小文字を区別しない構成で設定されている)の場合、 where
式は引き続きその要素と一致します。 ただし、 myAccounts
がメモリ内配列にある場合は一致しないため、合計で異なる結果が得られます。
しかし、ちょっと待ってください。 以前に文字列の比較について説明したとき、 ==
演算子が文字列の序数比較を実行することを確認しました。 では、なぜこの場合、 ==
演算子は大文字と小文字を区別しない比較を実行するのでしょうか。
答えは、LINQステートメントの基になるオブジェクトがSQLテーブルデータへの参照である場合(この例のEntity Framework DbSetオブジェクトの場合のように)、ステートメントはT-SQLステートメントに変換されるということです。 次に、演算子はC#プログラミングルールではなくT-SQLプログラミングルールに従うため、上記の場合の比較では大文字と小文字が区別されません。
一般に、LINQはオブジェクトのコレクションをクエリするための便利で一貫した方法ですが、実際には、コードの動作が確実に行われるように、ステートメントが内部でC#以外のものに変換されるかどうかを知る必要があります。実行時に期待どおりになります。
一般的なC#プログラミングの間違い#6:拡張メソッドによって混乱したり偽造されたりする
前述のように、LINQステートメントはIEnumerableを実装するすべてのオブジェクトで機能します。 たとえば、次の単純な関数は、アカウントのコレクションの残高を合計します。
public decimal SumAccounts(IEnumerable<Account> myAccounts) { return myAccounts.Sum(a => a.Balance); }
上記のコードでは、myAccountsパラメーターのタイプはIEnumerable<Account>
として宣言されています。 myAccounts
はSum
メソッドを参照するため(C#はおなじみの「ドット表記」を使用してクラスまたはインターフェイスのメソッドを参照します)、 IEnumerable<T>
インターフェイスの定義にSum()
というメソッドが表示されると予想されます。 ただし、 IEnumerable<T>
の定義は、 Sum
メソッドを参照せず、単純に次のようになります。
public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); }
では、 Sum()
メソッドはどこで定義されていますか? C#は強く型付けされているため、 Sum
メソッドへの参照が無効である場合、C#コンパイラは確実にエラーとしてフラグを立てます。 したがって、それが存在しなければならないことはわかっていますが、どこにあるのでしょうか。 さらに、LINQがこれらのコレクションをクエリまたは集約するために提供する他のすべてのメソッドの定義はどこにありますか?
答えは、 Sum()
はIEnumerable
インターフェイスで定義されたメソッドではないということです。 むしろ、 System.Linq.Enumerable
クラスで定義されているのは静的メソッド(「拡張メソッド」と呼ばれます)です。
namespace System.Linq { public static class Enumerable { ... // the reference here to “this IEnumerable<TSource> source” is // the magic sauce that provides access to the extension method Sum public static decimal Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal> selector); ... } }
では、拡張メソッドが他の静的メソッドと異なる点と、他のクラスで拡張メソッドにアクセスできるようにする理由は何でしょうか。

拡張メソッドの際立った特徴は、最初のパラメーターのthis
修飾子です。 これは、コンパイラに対して拡張メソッドとしてそれを識別する「魔法」です。 変更するパラメーターのタイプ(この場合はIEnumerable<TSource>
)は、このメソッドを実装しているように見えるクラスまたはインターフェースを示します。
(補足として、 IEnumerable
インターフェイスの名前と拡張メソッドが定義されているEnumerable
クラスの名前の類似性については、魔法のようなものは何もありません。この類似性は、任意のスタイル上の選択にすぎません。)
この理解により、上記で紹介したsumAccounts
関数が代わりに次のように実装されている可能性があることもわかります。
public decimal SumAccounts(IEnumerable<Account> myAccounts) { return Enumerable.Sum(myAccounts, a => a.Balance); }
代わりに、この方法で実装できたという事実は、なぜ拡張メソッドがあるのかという疑問を提起します。 拡張メソッドは基本的にC#プログラミング言語の便利な機能であり、新しい派生型を作成したり、再コンパイルしたり、元の型を変更したりすることなく、既存の型にメソッドを「追加」できます。
拡張メソッドは、 using [namespace];
ファイルの先頭にあるステートメント。 探している拡張メソッドがどのC#名前空間に含まれているかを知る必要がありますが、探しているものがわかれば、それを簡単に判断できます。
C#コンパイラは、オブジェクトのインスタンスでメソッド呼び出しを検出し、参照されているオブジェクトクラスで定義されているメソッドが見つからない場合、スコープ内にあるすべての拡張メソッドを調べて、必要なメソッドに一致するメソッドを見つけようとします。署名とクラス。 見つかった場合は、インスタンス参照をその拡張メソッドの最初の引数として渡し、残りの引数がある場合は、後続の引数として拡張メソッドに渡します。 (C#コンパイラがスコープ内に対応する拡張メソッドを見つけられない場合、エラーがスローされます。)
拡張メソッドは、C#コンパイラー側の「シンタックスシュガー」の例です。これにより、(通常は)より明確で保守しやすいコードを記述できます。 より明確に、つまり、それらの使用法を知っている場合。 そうしないと、特に最初は少し混乱する可能性があります。
拡張メソッドを使用することには確かに利点がありますが、それらを認識していない、または適切に理解していない開発者にとって、問題やC#プログラミングヘルプの叫びを引き起こす可能性があります。 これは、オンラインでコードサンプルを見るとき、または他の事前に作成されたコードを見るときに特に当てはまります。 そのようなコードがコンパイラエラーを生成する場合(呼び出されるクラスで明確に定義されていないメソッドを呼び出すため)、コードが別のバージョンのライブラリまたは別のライブラリに完全に適用されると考える傾向があります。 存在しない新しいバージョン、またはファントムの「欠落しているライブラリ」の検索に多くの時間が費やされる可能性があります。
拡張メソッドに精通している開発者でさえ、オブジェクトに同じ名前のメソッドがある場合、時々捕まることがありますが、そのメソッドのシグネチャは拡張メソッドのそれとは微妙に異なります。 そこにないタイプミスやエラーを探すのに多くの時間が無駄になる可能性があります。
C#ライブラリでの拡張メソッドの使用はますます普及しています。 LINQに加えて、UnityアプリケーションブロックとWeb APIフレームワークは、拡張メソッドも使用するMicrosoftによって頻繁に使用される2つの最新のライブラリの例であり、他にも多数あります。 フレームワークが最新であるほど、拡張メソッドが組み込まれる可能性が高くなります。
もちろん、独自の拡張メソッドを作成することもできます。 ただし、拡張メソッドは通常のインスタンスメソッドと同じように呼び出されるように見えますが、これは実際には単なる幻想であることに注意してください。 特に、拡張メソッドは、拡張しているクラスのプライベートメンバーまたは保護されたメンバーを参照できないため、従来のクラス継承の完全な代替として機能することはできません。
一般的なC#プログラミングの間違い#7:手元のタスクに間違ったタイプのコレクションを使用する
C#は多種多様なコレクションオブジェクトを提供しますが、以下は部分的なリストにすぎません。
Array
、 ArrayList
、 BitArray
、 BitVector32
、 Dictionary<K,V>
、 HashTable
、 HybridDictionary
、 List<T>
、 NameValueCollection
、 OrderedDictionary
、 Queue, Queue<T>
、 SortedList
、 Stack, Stack<T>
、 StringCollection
、 StringDictionary
。
選択肢が多すぎると選択肢が不十分になる場合もありますが、コレクションオブジェクトの場合はそうではありません。 利用可能なオプションの数は間違いなくあなたの利点に働くことができます。 事前に少し時間を取って調査し、目的に最適なコレクションタイプを選択してください。 これにより、パフォーマンスが向上し、エラーの余地が少なくなる可能性があります。
あなたが持っている要素のタイプ(文字列やビットなど)を特に対象としたコレクションタイプがある場合は、最初にそれを使用する傾向があります。 特定のタイプの要素を対象とする場合、実装は一般により効率的です。
C#の型安全性を利用するには、通常、非ジェネリックインターフェイスよりもジェネリックインターフェイスを優先する必要があります。 ジェネリックインターフェイスの要素は、オブジェクトを宣言するときに指定するタイプですが、非ジェネリックインターフェイスの要素はオブジェクトタイプです。 非ジェネリックインターフェイスを使用する場合、C#コンパイラはコードの型チェックを実行できません。 また、プリミティブ値タイプのコレクションを処理する場合、非ジェネリックコレクションを使用すると、それらのタイプのボックス化/アンボックス化が繰り返され、適切なタイプのジェネリックコレクションと比較した場合にパフォーマンスに重大な悪影響を与える可能性があります。
もう1つの一般的なC#の問題は、独自のコレクションオブジェクトを作成することです。 それが決して適切ではないというわけではありませんが、.NETが提供するものと同じくらい包括的な選択により、車輪の再発明ではなく、既存のものを使用または拡張することで、おそらく多くの時間を節約できます。 特に、C#およびCLI用のC5 Generic Collection Libraryは、永続的なツリーデータ構造、ヒープベースの優先度キュー、ハッシュインデックス付き配列リスト、リンクリストなど、「すぐに使える」さまざまな追加コレクションを提供します。
一般的なC#プログラミングの間違い#8:リソースの解放を怠る
CLR環境はガベージコレクターを採用しているため、オブジェクト用に作成されたメモリを明示的に解放する必要はありません。 実際、できません。 CにはC++のdelete
演算子やfree()
関数に相当するものはありません。 しかし、それはあなたがそれらを使い終わった後にあなたがただすべてのオブジェクトを忘れることができるという意味ではありません。 多くの種類のオブジェクトは、他の種類のシステムリソース(ディスクファイル、データベース接続、ネットワークソケットなど)をカプセル化します。 これらのリソースを開いたままにしておくと、システムリソースの総数がすぐに枯渇し、パフォーマンスが低下し、最終的にプログラムの障害が発生する可能性があります。
デストラクタメソッドは任意のC#クラスで定義できますが、デストラクタ(C#ではファイナライザとも呼ばれます)の問題は、いつ呼び出されるかを確実に知ることができないことです。 それらは、将来の不確定な時間にガベージコレクターによって(別のスレッドで呼び出され、追加の複雑さを引き起こす可能性があります)。 GC.Collect()
を使用してガベージコレクションを強制することでこれらの制限を回避しようとすることは、C#のベストプラクティスではありません。これは、収集に適格なすべてのオブジェクトを収集する間、スレッドを不明な時間ブロックするためです。
これは、ファイナライザーの適切な使用法がないということではありませんが、決定論的な方法でリソースを解放することはそれらの1つではありません。 むしろ、ファイル、ネットワーク、またはデータベース接続を操作しているときは、使用が終了したらすぐに、基盤となるリソースを明示的に解放する必要があります。
リソースリークは、ほとんどすべての環境で懸念されます。 ただし、C#は、堅牢で使いやすいメカニズムを提供します。これを使用すると、リークが非常にまれに発生する可能性があります。 .NET Frameworkは、 Dispose()
メソッドのみで構成されるIDisposable
インターフェイスを定義します。 IDisposable
を実装するオブジェクトは、オブジェクトのコンシューマーがオブジェクトの操作を終了するたびに、そのメソッドが呼び出されることを想定しています。 これにより、明示的で決定論的なリソースの解放が実現します。
単一のコードブロックのコンテキスト内でオブジェクトを作成および破棄する場合、C#はコードブロックに関係なくDispose()
が呼び出されることを保証するusing
ステートメントを提供するため、 Dispose()
の呼び出しを忘れることは基本的に許されません。終了します(例外、returnステートメント、または単にブロックの終了であるかどうか)。 はい、これは、ファイルの先頭にC#名前空間を含めるために使用される前述のusing
ステートメントと同じです。 これには、多くのC#開発者が気付いていない、まったく関係のない2番目の目的があります。 つまり、コードブロックが終了したときにDispose()
がオブジェクトで呼び出されるようにするには、次のようにします。
using (FileStream myFile = File.OpenRead("foo.txt")) { myFile.Read(buffer, 0, 100); }
上記の例でusing
ブロックを作成することにより、 Read()
が例外をスローするかどうかに関係なく、ファイルの処理が完了するとすぐにmyFile.Dispose()
が呼び出されることが確実にわかります。
一般的なC#プログラミングの間違い#9:例外を回避する
C#は、実行時に型安全性の実施を継続します。 これにより、C ++などの言語よりもはるかに迅速にC#の多くの種類のエラーを特定できます。この場合、型変換に誤りがあると、オブジェクトのフィールドに任意の値が割り当てられる可能性があります。 ただし、繰り返しになりますが、プログラマーはこの優れた機能を浪費し、C#の問題を引き起こす可能性があります。 C#は、例外をスローできる方法とスローしない方法の2つの異なる方法を提供するため、このトラップに分類されます。 try / catchブロックを作成する必要がないため、コーディングを節約できると考えて、例外ルートを敬遠する人もいます。
たとえば、C#で明示的な型キャストを実行する2つの異なる方法を次に示します。
// METHOD 1: // Throws an exception if account can't be cast to SavingsAccount SavingsAccount savingsAccount = (SavingsAccount)account; // METHOD 2: // Does NOT throw an exception if account can't be cast to // SavingsAccount; will just set savingsAccount to null instead SavingsAccount savingsAccount = account as SavingsAccount;
メソッド2の使用で発生する可能性のある最も明白なエラーは、戻り値のチェックの失敗です。 その結果、最終的にNullReferenceExceptionが発生する可能性があります。これは、かなり後で発生する可能性があり、問題の原因を突き止めるのがはるかに困難になります。 対照的に、メソッド1はInvalidCastException
をすぐにスローし、問題の原因をよりすぐに明らかにします。
さらに、メソッド2で戻り値を確認することを覚えていても、nullであることがわかった場合はどうしますか? あなたが書いている方法は、エラーを報告するのに適切な場所ですか? そのキャストが失敗した場合に試すことができる他の何かがありますか? そうでない場合は、例外をスローするのが正しいことなので、問題の原因のできるだけ近くで例外を発生させることをお勧めします。
次に、一方が例外をスローし、もう一方が例外をスローしない、他の一般的なメソッドのペアの例をいくつか示します。
int.Parse(); // throws exception if argument can't be parsed int.TryParse(); // returns a bool to denote whether parse succeeded IEnumerable.First(); // throws exception if sequence is empty IEnumerable.FirstOrDefault(); // returns null/default value if sequence is empty
一部のC#開発者は「例外が不利」であるため、例外をスローしないメソッドが優れていると自動的に想定します。 これが当てはまる場合もありますが、一般化としてはまったく正しくありません。
具体的な例として、例外が生成された場合に実行する代替の正当な(たとえば、デフォルトの)アクションがある場合、例外ではないアプローチが正当な選択である可能性があります。 そのような場合、実際には次のように書く方が良いかもしれません。
if (int.TryParse(myString, out myInt)) { // use myInt } else { // use default value }
それ以外の:
try { myInt = int.Parse(myString); // use myInt } catch (FormatException) { // use default value }
ただし、したがって、 TryParse
が必然的に「より良い」メソッドであると想定するのは誤りです。 そうなることもあれば、そうでないこともあります。 そのため、2つの方法があります。 例外は開発者としての友だちになる可能性があることを忘れないでください。
一般的なC#プログラミングの間違い#10:コンパイラの警告を蓄積できるようにする
この問題はC#固有のものではありませんが、C#コンパイラが提供する厳密な型チェックの利点を放棄するため、C#プログラミングでは特に深刻です。
警告は理由で生成されます。 すべてのC#コンパイラエラーはコードの欠陥を示していますが、多くの警告も同様です。 この2つを区別するのは、警告が発生した場合、コンパイラーはコードが表す命令を問題なく出力できることです。 それでも、コードが少し怪しいと感じ、コードが意図を正確に反映していない可能性があります。
このC#プログラミングチュートリアルの一般的な簡単な例は、使用していた変数の使用を排除するようにアルゴリズムを変更したが、変数宣言を削除するのを忘れた場合です。 プログラムは完全に実行されますが、コンパイラは役に立たない変数宣言にフラグを立てます。 プログラムが完全に実行されるという事実により、プログラマーは警告の原因を修正することを怠ります。 さらに、コーダーはVisual Studioの機能を利用して、[エラーリスト]ウィンドウで警告を簡単に非表示にして、エラーのみに集中できるようにします。 何十もの警告が出るまで、それほど時間はかかりません。それらはすべて、幸いにも無視されます(さらに悪いことに、隠されます)。
しかし、遅かれ早かれ、このタイプの警告を無視すると、次のようなものがコードに組み込まれる可能性があります。
class Account { int myId; int Id; // compiler warned you about this, but you didn't listen! // Constructor Account(int id) { this.myId = Id; // OOPS! } }
そして、Intellisenseがコードを記述できる速度で、このエラーは見た目ほど起こりそうにありません。
プログラムに重大なエラーが発生しました(コンパイラは警告としてフラグを立てただけですが、すでに説明した理由により)。プログラムの複雑さによっては、これを追跡するのに多くの時間を浪費する可能性があります。 そもそもこの警告に注意を払っていれば、5秒の簡単な修正でこの問題を回避できたでしょう。
C Sharpコンパイラは、コードの堅牢性に関する多くの有用な情報を提供することを忘れないでください…聞いている場合。 警告を無視しないでください。 通常、修正には数秒しかかかりません。新しいものが発生したときに修正すると、時間を節約できます。 Visual Studioの「エラーリスト」ウィンドウに「0エラー、0警告」が表示されることを期待するようにトレーニングしてください。そうすれば、警告が発生すると、すぐに対処するのに十分な不快感を覚えることになります。
もちろん、すべてのルールには例外があります。 したがって、意図したとおりのコードであっても、コードがコンパイラーにとって少し怪しげに見える場合があります。 これらの非常にまれなケースでは、警告をトリガーするコードと、それがトリガーする警告IDに対してのみ、 #pragma warning disable [warning id]
を使用してください。 これにより、その警告が抑制され、その警告のみが抑制されるため、新しい警告に引き続き注意を払うことができます。
要約
C#は、生産性を大幅に向上させることができる多くのメカニズムとパラダイムを備えた強力で柔軟な言語です。 ただし、他のソフトウェアツールや言語と同様に、その機能の理解や認識が限られていると、メリットよりも障害になることがあり、「危険なことを十分に知っている」ということわざの状態になります。
このようなCシャープのチュートリアルを使用して、この記事で提起された問題など、C#の重要なニュアンスに慣れることで、C#の最適化に役立ち、一般的な落とし穴のいくつかを回避できます。言語。
Toptal Engineeringブログでさらに読む:
- 重要なC#インタビューの質問
- C#とC ++:コアとは何ですか?