C#について知らなかった8つの事実

以下は、C#言語に関する珍しい事実で、ほとんど開発者が知らないものです。



1.インデクサーはparamsパラメーターを使用できます



私たちは皆、x =何か["a"]インデクサーが通常どのように見えるか、そしてそれを実装するために必要なコードを知っています:



public string this[string key] { get { return internalDictionary[key]; } }
      
      





しかし、要素にアクセスするには、paramsパラメータx = something ["a"、 "b"、 "c"、 "d"]を使用できることを知っていましたか?

インデクサーを次のように記述します。



 public IEnumerable<string> this[params string[] keys] { get { return keys.Select(key => internalDictionary[key]).AsEnumerable(); } }
      
      





素晴らしいニュースは、同じクラスで複数のインデクサーを一緒に使用できることです。 配列またはいくつかの引数をパラメーターとして渡すと、結果としてIEnumerableが取得されますが、1つのパラメーターを持つインデクサーを呼び出すことにより、単一の値が取得されます。



2.コードで定義された文字列リテラルは単一のコピーに保存されます



多くの開発者は次のコードを考えています:



 if (x == "" || x == "y")
      
      





毎回数行を作成します。 いいえ、できません。 C#は、他の多くのプログラミング言語と同様に文字列インターンを使用し、アプリケーションで使用される各リテラル文字列は、文字列プールと呼ばれる特別なリスト(ハッシュテーブル)に入れられ、実行時にリンクが取得されます。



String.IsInternedメソッドを使用して、文字列が現時点で収容プールにあるかどうかを判別できますが、式String.IsInterned( "what")== "what"が常に真であることを忘れないでください。

最適化のためのコンパイラのおかげで、 String.IsInterned( "wh" + "at")== "what"も常に真になります。 String.IsInterned(新しい文字列(新しい文字[] {'w'、 'h'、 'a'、 't'})==新しい文字列(新しい文字[] {'w'、 'h'、 'a' 、 't'})は、リテラル文字列 "what"が既にコードで使用されている場合、またはそれが手動で収容プールに追加された場合にのみ真になります。



定期的に文字列を使用するクラスがある場合は、String.Internメソッドを使用して文字列プールに追加することを検討してください。 ただし、この場合はアプリケーションが終了するまで保存されるため、String.Internを慎重に使用してください。 文字列をプールに追加するには、単にString.Intern(someClass.ToString())メソッドを呼び出します。 もう1つの注意点は、インターンのおかげで、(オブジェクト) "Hi" ==(オブジェクト) "Hi"は常に真であるということです。 解釈ウィンドウでこのコードを確認してください。デバッガーは行をインターンしないため、結果は負になります。



3.型を特殊化されていない型にキャストしても、実際の型を使用できなくなるわけではありません。



これのすばらしい例は、IEnumerableを返すプロパティです。実際の型は、たとえば、次のとおりです。



 private readonly List<string> internalStrings = new List<string>(); public IEnumerable<string> AllStrings { get { return internalStrings; }
      
      





誰も文字列のリストを変更できないと思います。 残念ながら、これは非常に簡単です。



 ((List<string>)x.AllStrings).Add("Hello");
      
      





AsEnumerableメソッドでさえ、ここでは役に立ちません。 リストのラッパーを作成し、変更しようとするたびに例外をスローするAsReadOnlyメソッドを使用できますが、クラスで同様のことを行う良いパターンも提供します。



4.メソッドの変数は、中括弧で制限されたスコープを持つことができます



Pascalプログラミング言語では、関数の開始時に使用されるすべての変数を記述する必要があります。 幸いなことに、今日、変数宣言は割り当ての隣に存在する可能性があるため、意図せずに誤って使用されることはありません。



ただし、これにより、それらが不要になった後にそれらを使用することを防ぐことはできません。 / if / while / usingのキーワードを使用して、一時変数を記述することができます。 驚くでしょうが、同じ結果を得るためにキーワードなしで中括弧を使用して一時変数を記述することもできます:



 private void MultipleScopes() { { var a = 1; Console.WriteLine(a); } { var b = 2; Console.WriteLine(a); } }
      
      





このコードは、中括弧で囲まれた変数を参照するため、2行目のためにコンパイルされません。 このようにメソッドを中括弧付きの部分に分割することは非常に便利ですが、より良い解決策は、リファクタリングメソッドを使用してメソッドを小さなメソッドに分割することです:メソッドを抽出します。



5.列挙には拡張メソッドがあります



拡張メソッドは、既存のクラスのメソッドを記述して、チームの人々がそれらを使用できるようにする機能を提供します。 列挙がクラス(より正確には構造体)に似ていることを考えると、それらが拡張できることに驚かないでください。例えば:



 enum Duration { Day, Week, Month }; static class DurationExtensions { public static DateTime From(this Duration duration, DateTime dateTime) { switch (duration) { case Duration.Day: return dateTime.AddDays(1); case Duration.Week: return dateTime.AddDays(7); case Duration.Month: return dateTime.AddMonths(1); default: throw new ArgumentOutOfRangeException("duration"); } } }
      
      





列挙は悪だと思いますが、少なくとも拡張メソッドはコード内の条件付きチェック(switch / if)を取り除くことができます。 列挙値が正しい範囲にあることを忘れずに確認してください。



6.静的変数を記述する順序は重要です



一部の人々は、変数はアルファベット順に配置されていると主張し、あなたのためにそれらの順序を変更できる特別なツールがあります。 ただし、順序を変更するとアプリケーションが破損するシナリオがあります。



 static class Program { private static int a = 5; private static int b = a; static void Main(string[] args) { Console.WriteLine(b); } }
      
      





このコードは数値5を出力します。変数aとbの説明を入れ替えると、0が出力されます。



7.プライベートクラスのインスタンス変数は、このクラスの別のインスタンスで使用できる場合があります



次のコードは機能しないと思われるかもしれません。



 class KeepSecret { private int someSecret; public bool Equals(KeepSecret other) { return other.someSecret == someSecret; } }
      
      





privateはクラスの特定のインスタンスのみを意味し、アクセスできると考えるのは簡単ですが、実際には、これはこのクラスのみがアクセスできることを意味します...このクラスの他のインスタンスを含みます。 実際、これはいくつかの比較方法を実装するときに非常に便利です。



8. C#言語仕様は既にコンピューター上にあります



Visual Studioがインストールされている場合は、VC#\ Specificationsフォルダーで仕様を見つけることができます。 VS 2011には、Word形式のC#5.0言語仕様が付属しています。



以下のような多くの興味深い事実がいっぱいです。





そして、「私はこれのすべて/ほとんどを知っていました」と言う人に、「開発者を雇うとき、どこにいますか?」と言います。 まじめな話、言語をよく知っているC#開発者を見つけるのは難しい。



自分から、いくつかの事実を追加します。


9.列挙ではvalue__という名前の定数を使用できません



この事実は多くの開発者に知られていると思います。 列挙がコンパイルされる内容を知るだけで十分な理由を理解するため。



次のリスト:



 public enum Language { C, Pascal, VisualBasic, Haskell }
      
      





コンパイラは次のようなものを見ます:



 public struct Language : Enum { // ,      public const Language C = (Language)0; public const Language Pascal = (Language)1; public const Language VisualBasic = (Language)2; public const Language Haskell = (Language)3; //      Language //        public Int32 value__; }
      
      





これで、変数名value__が単に列挙の現在の値を保存するために予約されていることは明らかです。



10.フィールド初期化子が多すぎます



フィールド初期化子がどのように機能するかは誰もが知っています。



 class ClassA { public string name = "Tim"; public int age = 20; public ClassA() { } public ClassA(string name, int age) { this.name = name; this.age = age; } }
      
      





初期化コードは、各コンストラクターに配置されます。



 class ClassA { public string name; public int age; public ClassA() { this.name = "Tim"; this.age = 20; base..ctor(); } public ClassA(string name, int age) { this.name = "Tim"; this.age = 20; base..ctor(); this.name = name; this.age = age; } }
      
      





しかし、あるコンストラクターが別のコンストラクターを呼び出す場合、初期化は呼び出されたコンストラクターにのみ配置されます...



 class ClassA { public string name = "Tim"; public int age = 20; public ClassA() { } public ClassA(string name, int age):this() { this.name = name; this.age = age; } }
      
      





同等です



 class ClassA { public string name; public int age; public ClassA() { this.name = "Tim"; this.age = 20; base..ctor(); } public ClassA(string name, int age) { this..ctor(); this.name = name; this.age = age; } }
      
      





そして今、問題は、デザイナーがお互いに電話をかけるとどうなりますか? コンパイラは初期化コードをどこに配置することを決定しますか?



回答:コンパイラーは、最初のコンストラクターが2番目のコンストラクターを呼び出すため、最初のコンストラクターに初期化を設定しないことを決定します。 彼は2人目のデザイナーと同様の結論を出します。 その結果、初期化コードはどのコンストラクタにも配置されません...そして、どこにも配置する必要がないので、なぜエラーをチェックする必要がありますか? したがって、初期化コードには、必要なものを何でも書くことができます。 例えば



 class ClassA { public string name = 123456789; public int age = string.Empty; public ClassA():this("Alan", 25) { } public ClassA(string name, int age):this() { this.name = name; this.age = age; } }
      
      





このコードは問題なくコンパイルされますが、このクラスのインスタンスを作成するとスタックオーバーフローでクラッシュします。



11.列挙の基本型を記述する場合、C#シノニムのみを使用できます



ご存知のように、列挙を記述するとき、その基礎となる基本型を明示的に示すことができます。 デフォルトはintです。



このように書くことができます



 public enum Language : int { C, Pascal, VisualBasic, Haskell }
      
      





しかし、そのように書くことはできません



 public enum Language : Int32 { C, Pascal, VisualBasic, Haskell }
      
      





コンパイラには、C#タイプの同義語が必要です。



12.構造体はコレクションで使用される場合不変です



ご存じのとおり、.NETの構造は重要なタイプです。つまり、値によって渡される(コピーされる)ため、可変構造は悪であると言えます。 ただし、変更可能な構造が悪であるという事実は、変更できないという意味ではありません。 デフォルトでは、構造は変更可能です。つまり、構造を変更できます。 しかし、構造体がコレクションで使用される場合、それらは可変でなくなります。 コードを考慮してください:



 struct Point { public int x; public int y; } static void Main(string[] args) { var p = new Point(); px = 5; py = 9; var list = new List<Point>(10); list.Add(p); list[0].x = 90;//  var array = new Point[10]; array[0] = p; array[0].x = 90;//  }
      
      





最初のケースでは、コレクションインデクサーは構造体のコピーを返す単なるメソッドであるため、コンパイルエラーが発生します。 2番目のケースでは、配列のインデックス作成はメソッド呼び出しではなく、適切な要素へのアピールであるため、エラーは発生しません。



読んでくれてありがとう。

この記事がお役に立てば幸いです。



All Articles