面白いC#

面白いC#



PVS-StudioコードのC#アナライザーの診断の品質を評価するために、多数の異なるプロジェクトをチェックします。 なぜなら プロジェクトはさまざまな企業のさまざまなチームのさまざまな人々によって書かれているため、さまざまなスタイル、略語、C#言語がプログラマに提供する可能性に対処する必要があります。 この記事では、素晴らしいC#言語が提供するいくつかのポイントと、それを使用するときに遭遇する可能性のある問題を確認します。



写真1








発言。

この記事は、好奇心に焦点を当てており、個人的に興味深いと思うことについて説明しています。



プロパティとそれらでできること



プロパティーは、いくつかのフィールドの値を変更および読み取るためのアクセサーとミューテーターという2つの関数であることは誰もが知っています。 まあ、または少なくともそれはC#3.0言語バージョンの前でした。 つまり 古典的には、次のようになります。

class A { int index; public int Index { get { return index; } set { index = value; } } }
      
      





数年が経ち、言語の標準とさまざまな可能性で生い茂ったプロパティ。



少し始めましょう。 C#3.0標準では、フィールドを省略する有名な機会があります。 このように書く:

 class A { public int Index { get; set; } }
      
      





C#6.0では、さらに進んで「セット」を削除しました。

 class A { public int Index { get; } }
      
      





C#6.0より前にこの方法で記述することはできましたが、そのような変数に何かを書き込むことはできませんでした。 これは、実際には、 読み取り専用フィールドに類似しています 。 このようなプロパティの値は、コンストラクターでのみ設定できます。



プロパティとフィールドはさまざまな方法で初期化できます。 たとえば、次のように:

 class A { public List<int> Numbers { get; } = new List<int>(); }
      
      





まあまあ:

 class A { public List<int> Numbers = new List<int>(); }
      
      





そして、あなたはこのように書くことができます:

 class A { public List<int> Numbers => new List<int>(); }
      
      





後者の場合、不快な驚きがあなたを待っています。 実際、最後の例では、このプロパティを作成しました。

 class A { public List<int> Numbers { get { return new List<int>(); } } }
      
      





つまり Numbersに入力しようとすると、新しいリストが作成されるたびに、原則として成功しません。

 A a = new A(); a.Numbers.Add(10); a.Numbers.Add(20); a.Numbers.Add(30);
      
      





記録を短くする場合は注意してください。これにより、非常に長いエラー検索が行われる場合があります。



プロパティの興味深いプロパティはそこで終わりません。 前述したように、プロパティはいくつかの関数であり、関数では誰もそこに来るパラメーターを変更する必要はありません。



次のコードは問題なくコンパイルされ、動作します。

 class A { int index; public int Index { get { return index; } set { value = 20; index = value; } } } static void Main(string[] args) { A a = new A(); a.Index = 10; Console.WriteLine(a.Index); }
      
      





作業の結果は、「10」ではなく「20」という数字の出力になります。



なぜ誰かが20の価値を書くことをあきらめたのでしょうか? これでさえも洗い流すことができることがわかりました。 ただし、この意味を明確にするために、プロパティから少し注意をそらし、キーシンボル@について説明します。 このキーシンボルを使用すると、キーワードを使用した記述と同様の変数を作成できます。 例: @ this@operatorなど。 しかし、このシンボルは、魂が望む場所に突き出すことを誰も禁じていません、例えば:

 class A { public int index; public void CopyIndex(A @this) { this.@index = @this.index; } } static void Main(string[] args) { A a = new A(); @a.@index = 10; a.@CopyIndex(new A() { @index = 20 }); Console.WriteLine(a.index); }
      
      





この記事でいつものように、作業の結果は「10」ではなく「20」という数字の結論になります。



実際、 @記号は、 CopyIndex関数で@thisパラメーターの名前を記述するときに1か所でのみ必要です。 他の場所では、これは単に冗長なコードであり、さらに、何が書かれているのかを理解することを困難にします。



この知識があるので、プロパティに戻り、次のクラスがあると仮定します。

 class A { int value; public int Value { get { return @value; } set { @value = value; } } public A() { value = 5; } }
      
      





クラスAの フィールドがValueプロパティで変更されると考えるかもしれません。 しかし、実際には、これは起こらず、次のプログラムの結果は10ではなく5になります。

 static void Main(string[] args) { A a = new A(); a.Value = 10; Console.WriteLine(a.Value); }
      
      





この動作は、 getの @valuesetの @valueの不一致によるものです。 getの@valueはクラスAフィールドにすぎません。 また、 セット内の@valueは、実際にはセット関数のパラメーターです。 したがって、単純にを書き込むだけで、クラスAの フィールドには一切影響しません。



コレクションを初期化する



最初に、配列を初期化するさまざまな方法を思い出しましょう。

 string[] test1 = new string[] { "1", "2", "3" }; string[] test2 = new[] { "1", "2", "3" }; string[] test3 = { "1", "2", "3" }; string[,] test4 = { { "11", "12" }, { "21", "22" }, { "31", "32" } };
      
      





リストは簡単で、初期化オプションは1つだけです。

 List<string> test2 = new List<string>(){ "1", "2", "3" };
      
      





そして最後に、 辞書

 Dictionary<string, int> test = new Dictionary<string, int>() { { "aa", 1 }, { "bb", 2 }, { "cc", 3 } };
      
      





しかし、次の方法のために、初めて見たので、このセクションを書きました。

 Dictionary<string, int> test = new Dictionary<string, int>() { ["aa"] = 1, ["bb"] = 2, ["cc"] = 3 };
      
      





LINQクエリについて少し



LINQクエリは基本的にそれ自体が便利なものです。 必要なサンプルを使用してチェーンを収集し、出力で必要な情報を取得します。 最初に、自分で確認するまで発生しない可能性のあるいくつかの楽しい瞬間について説明します。 始めるために、基本的な例を考えてみましょう。

 void Foo(List<int> numbers1, List<int> numbers2) { var selection1 = numbers1.Where(index => index > 10); var selection2 = numbers2.Where(index => index > 10); }
      
      





上記の例には、いくつかの同一のチェックがあることがわかります。 つまり、良い方法で、それらを別の「関数」に取り出すことができます。

 void Foo(List<int> numbers1, List<int> numbers2) { Func<int, bool> whereFunc = index => index > 10; var selection1 = numbers1.Where(index => whereFunc(index)); var selection2 = numbers2.Where(index => whereFunc(index)); }
      
      





関数が大きい場合、それはすでに改善されており、一般的には問題ありません。 whereFuncの呼び出しは少しわかりにくいです。ちょっといです。 これも実際には問題ではありません。

 void Foo(List<int> numbers1, List<int> numbers2) { Func<int, bool> whereFunc = index => index > 10; var selection1 = numbers1.Where(whereFunc); var selection2 = numbers2.Where(whereFunc); }
      
      





今では簡潔できれいです。



LINQ式のニュアンスについて少し説明します。 たとえば、コードの行は、 numbers1コレクションからデータを即座にフェッチしません。

 IEnumerable<int> selection = numbers1.Where(whereFunc);
      
      





データサンプリングは、シーケンスがList <int>コレクションに変換されたときにのみ開始されます。

 List<int> listNumbers = selection.ToList();
      
      





作業のこのニュアンスは、値が変更された後、キャプチャされた変数の使用に簡単につながる可能性があります。 簡単な例を見てみましょう。 配列 "{1、2、3、4、5}"から、要素のインデックスより小さい数値を持つ要素のみを返す関数Fooが必要であると仮定します。

 0 : 1 : 2 : 1 3 : 1, 2 4 : 1, 2, 3
      
      





彼女の署名を次のようにします。

 static Dictionary<int, IEnumerable<int>> Foo(int[] numbers) { .... }
      
      





そして、呼び出しは次のようになります。

 foreach (KeyValuePair<int, IEnumerable<int>> subArray in Foo(new[] { 1, 2, 3, 4, 5 })) Console.WriteLine(string.Format("{0} : {1}", subArray.Key, string.Join(", ", subArray.Value)));
      
      





すべてがシンプルなようです。 それでは、LINQベースの実装自体を記述しましょう。 次のようになります。

 static Dictionary<int, IEnumerable<int>> Foo(int[] numbers) { var result = new Dictionary<int, IEnumerable<int>>(); for (int i = 0; i < numbers.Length; i++) result[i] = numbers.Where(index => index < i); return result; }
      
      





ご覧のとおり、すべてが非常に簡単です。 numbers配列からサンプルを取得し、交互に「作成」します。



このようなプログラムの結果は、コンソールに次のようなテキストが表示されます。

 0 : 1, 2, 3, 4 1 : 1, 2, 3, 4 2 : 1, 2, 3, 4 3 : 1, 2, 3, 4 4 : 1, 2, 3, 4
      
      





ここでの問題は、ラムダインデックス=>インデックス<iで発生したクロージャです。 変数iがキャプチャされましたが、式のラムダindex => index <istring.Join( "、"、subArray.Value)関数で結果を要求する瞬間まで発生しなかったため、その中の値は同じではありませんでしたLINQ要求の生成時。 サンプルからデータを受信するとき、 iの値は5でした。これは、誤った出力結果をもたらしました。



Cで文書化されていない松葉杖#



C ++は、ハッキング、回避策、およびその他の松葉杖で知られています。これは、 XXX_castシリーズの機能に値します。 C#ではこれはそうではないと考えられています。 実際、これは完全に真実ではありません...



いくつかの言葉から始めましょう。

これらの単語はIntelliSenseにはなく、MSDNにはそれらの公式の説明はありません。



それでは、これらはどんな奇跡的な言葉なのでしょうか?



__makerefはオブジェクトを受け取り、 TypedReference型のオブジェクトとしてオブジェクトへの一種の「リンク」を返します。 そして実際、 __ reftype__refvalueという言葉は、それぞれこの「リンク」から、オブジェクトのタイプとこの「リンク」からのオブジェクトの値を学ぶことを可能にします。



例を考えてみましょう:

 struct A { public int Index { get; set; } } static void Main(string[] args) { A a = new A(); a.Index = 10; TypedReference reference = __makeref(a); Type typeRef = __reftype(reference); Console.WriteLine(typeRef); //=> ConsoleApplication23.Program+A A valueRef = __refvalue(reference, A); Console.WriteLine(valueRef.Index); //=> 10 }
      
      





しかし、そのような「耳のフェイント」は、もう少し有名な手段にすることができます。

 static void Main(string[] args) { A a = new A(); a.Index = 10; dynamic dynam = a; Console.WriteLine(dynam.GetType()); A valuDynam = (A)dynam; Console.WriteLine(valuDynam.Index); }
      
      





dynamicを使用すると 、行数が少なくなり、質問が少なくなります-「これは何ですか?」「どのように機能しますか?」 しかし、ここでは、 ダイナミックの操作TypedReferenceの場合ほど良く見えない、わずかに異なるシナリオがあります。

 static void Main(string[] args) { TypedReference reference = __makeref(a); SetVal(reference); Console.WriteLine(__refvalue(reference, A).Index); } static void SetVal(TypedReference reference) { __refvalue(reference, A) = new A() { Index = 20 }; }
      
      





作業の結果は、数字「20」のコンソールへの結論になります。 はい、 dynamicrefを介して関数に渡すこともでき、同様に機能します。

 static void Main(string[] args) { dynamic dynam = a; SetVal(ref dynam); Console.WriteLine(((A)dynam).Index); } static void SetVal(ref dynamic dynam) { dynam = new A() { Index = 20 }; }
      
      





私の意見では、 TypedReferenceのオプションは、特に情報を次々とスキップして機能をスキップする場合に、 見栄えがよくなります。



上記に加えて、別の奇跡の単語__arglistがあります。これにより、可変数のパラメーター、さらには任意の型を持つ関数を作成できます。

 static void Main(string[] args) { Foo(__arglist(1, 2.0, "3", new A[0])); } public static void Foo(__arglist) { ArgIterator iterator = new ArgIterator(__arglist); while (iterator.GetRemainingCount() > 0) { TypedReference typedReference = iterator.GetNextArg(); Console.WriteLine("{0} / {1}", TypedReference.ToObject(typedReference), TypedReference.GetTargetType(typedReference)); } }
      
      





奇妙なことは、 foreachを使用してボックスから要素を通過させることはできず、リストから要素に直接アクセスできないことです。 そのため、 引数を使用してC ++やJavaScriptに到達しません。

 function sum() { .... for(var i=0; i < arguments.length; i++) s += arguments[i] }
      
      





さらに、私が実際にそれらの言葉に出会ったときにこれらの言葉が何であるかを見つけた記事リンクを提供します。



おわりに



結論として、C ++とC#はどちらも非常に文法のない言語であるため、一方では便利ですが、他方ではタイプミスを防ぐことはできません。 C#では、C ++のように間違いを犯してはならないという根深い信念があります。実際、これはまったくそうではありません。 この記事では、私の意見では非常に興味深い言語の機能が紹介されていますが、C#のエラーの大部分はそれらにありませんが、たとえばInfragisticsプロジェクトのように通常の帰納法を書く場合です。

 public bool IsValid { get { var valid = double.IsNaN(Latitude) || double.IsNaN(Latitude) || this.Weather.DateTime == Weather.DateTimeInitial; return valid; } }
      
      





V3001 「||」の左と右に同一の副次式「double.IsNaN(Latitude)」があります。 演算子。 WeatherStation.cs 25



注意はほとんどの場合、まさにそのような瞬間に散らばり、それから「どこでなぜ明確でないのか」という長い検索が行われます。 PVS-Studioコードアナライザーを使用して、エラーから身を守る機会をお見逃しなく。






All Articles