C#または.NETの最悪の問題は何ですか?[閉まっている]


377

私は最近DateTimeオブジェクトを操作していて、次のようなものを書きました:

DateTime dt = DateTime.Now;
dt.AddDays(1);
return dt; // still today's date! WTF?

のintellisenseのドキュメントでAddDays()は、日付に1日を追加しますが、実際に追加しません。実際に、日付を追加した日付を返すため、次のように記述する必要があります。

DateTime dt = DateTime.Now;
dt = dt.AddDays(1);
return dt; // tomorrow's date

これは以前に何度も私をかみましたので、最悪のC#の落とし穴をカタログ化すると便利だと思いました。


157
DateTime.Now.AddDays(1);を返します。
crashmstr 2008年

23
AFAIK、組み込みの値タイプはすべて不変です。少なくとも、タイプに含まれるメソッドは、既存のアイテムを変更するのではなく、新しいアイテムを返します。少なくとも、これを行わない頭の上の1つは考えられません。
Joel Coehoorn、2008年

6
変更可能な値の型:System.Collections.Generics.List.Enumerator :((はい、十分に頑張れば奇妙に動作するのがわかります。)
Jon Skeet

13
インテリセンスは、必要なすべての情報を提供します。これは、DateTimeオブジェクトを返すと言っています。渡したものを変更しただけの場合、それはvoidメソッドです。
John Kraft、

20
必ずしもそうとは限りません。たとえば、StringBuilder.Append(...)は「this」を返します。これは流暢なインターフェイスではよくあることです。
Jon Skeet、

回答:


304
private int myVar;
public int MyVar
{
    get { return MyVar; }
}

ブラモ。アプリはスタックトレースなしでクラッシュします。いつも起こります。

(ゲッターMyVarでは小文字myVarではなく大文字に注意してください。)


112
そして、このサイトにふさわしい:)
gbjbaanb '27 / 10/27

62
プライベートメンバーにアンダースコアを付けて、大いに役立ちます!
chakrit 2008年

61
私はできる限り自動プロパティを使用して、この種の問題を
たくさん止めてい

28
これは、プライベートフィールドにプレフィックスを使用する大きな理由です(他にもありますが、これは良いものです):_myVar、m_myVar
jrista '26

205
@jrista:Oお願いしますNO ... m_ではありません... arrh the horror ...
fretje

254

Type.GetType

たくさんの人に噛まれたのを見たことがあるType.GetType(string)。彼らはなぜそれが独自のアセンブリの型に対して機能するのか、のような一部の型に対して機能するのか疑問に思ってSystem.StringSystem.Windows.Forms.Formます。答えは、現在のアセンブリとのみを検索することmscorlibです。


匿名メソッド

C#2.0は匿名メソッドを導入し、次のような厄介な状況を引き起こしました。

using System;
using System.Threading;

class Test
{
    static void Main()
    {
        for (int i=0; i < 10; i++)
        {
            ThreadStart ts = delegate { Console.WriteLine(i); };
            new Thread(ts).Start();
        }
    }
}

それは何を出力しますか?まあ、それは完全にスケジュールに依存します。10個の数値が出力されますが、おそらく0、1、2、3、4、5、6、7、8、9は出力されないでしょう。問題はi、デリゲートの作成時点での値ではなく、取得された変数であることです。これは、適切なスコープの追加のローカル変数で簡単に解決できます。

using System;
using System.Threading;

class Test
{
    static void Main()
    {
        for (int i=0; i < 10; i++)
        {
            int copy = i;
            ThreadStart ts = delegate { Console.WriteLine(copy); };
            new Thread(ts).Start();
        }
    }
}

イテレータブロックの遅延実行

この「貧乏人の単体テスト」はパスしません-なぜパスしないのですか?

using System;
using System.Collections.Generic;
using System.Diagnostics;

class Test
{
    static IEnumerable<char> CapitalLetters(string input)
    {
        if (input == null)
        {
            throw new ArgumentNullException(input);
        }
        foreach (char c in input)
        {
            yield return char.ToUpper(c);
        }
    }

    static void Main()
    {
        // Test that null input is handled correctly
        try
        {
            CapitalLetters(null);
            Console.WriteLine("An exception should have been thrown!");
        }
        catch (ArgumentNullException)
        {
            // Expected
        }
    }
}

答えは、コードのソース内のCapitalLettersコードは、イテレータのMoveNext()メソッドが最初に呼び出されるまで実行されないということです。

私の頭の体操のページに他のいくつかの奇妙な点があります


25
イテレータの例はだまされています!
ジミー、

8
これを3つの答えに分けて、一緒に投票するのではなく、それぞれに投票できるようにしないでください。
chakrit 2008年

13
@chakrit:振り返ってみると、それはおそらく良い考えだったでしょうが、今は遅すぎると思います。それはまた、私がより多くの回答を得ようとしていたように見えたかもしれません...
ジョン・スキート

19
実際にType.GetTypeは、AssemblyQualifiedNameを指定した場合に機能します。Type.GetType( "System.ServiceModel.EndpointNotFoundException、System.ServiceModel、Version = 3.0.0.0、Culture = neutral、PublicKeyToken = b77a5c561934e089");
chilltemp 2008年

2
@kentaromiura:オーバーロードの解決は、最も派生した型から始まり、ツリーを処理します。ただし、対象の型で最初に宣言されたメソッドのみを調べます。Foo(int)は基本メソッドをオーバーライドするため、考慮されません。Foo(object)が適用されるため、オーバーロードの解決はそこで停止します。奇妙なことです。
Jon Skeet、

194

例外の再スロー

多くの新規開発者を獲得する落とし穴は、再スロー例外セマンティクスです。

多くの場合、次のようなコードが表示されます

catch(Exception e) 
{
   // Do stuff 
   throw e; 
}

問題は、スタックトレースを消去し、問題の診断をはるかに困難にすることです。これにより、例外の発生場所を追跡できなくなります。

正しいコードは、引数のないthrowステートメントです。

catch(Exception)
{
    throw;
}

または、例外を別の例外でラップし、内部例外を使用して元のスタックトレースを取得します。

catch(Exception e) 
{
   // Do stuff 
   throw new MySpecialException(e); 
}

幸いなことに、私は最初の1週間で誰かからこれについて教えを受け、より上級の開発者のコ​​ードで見つけました。Is:catch(){throw; } 2番目のコードスニペットと同じですか?catch(Exception e){スロー; }例外オブジェクトを作成してそれを設定しないだけですか?
StuperUser 2009

単にスローするのではなく、throw ex(またはthrow e)を使用することのエラーに加えて、例外をキャッチして再度スローするだけの価値があるのはどのような場合があるのか​​疑問に思います。
ライアンランディ

13
@Kyralessa:多くの場合があります。たとえば、トランザクションをロールバックしたい場合、呼び出し元が例外を受け取る前に。ロールバックしてから再スローします。
R.マルティーニョフェルナンデス

7
すべての例外をキャッチする必要があると教えられただけで人々が例外をキャッチして再スローするとき、これはいつも見られます。コールスタックのさらに上でキャッチされることを認識していません。それは私を狂わせる。
James Westgate

5
@Kyralessa最大のケースは、ロギングを行う必要がある場合です。...キャッチでのエラーを記録し、再スロー
nawfal

194

ハイゼンベルク時計ウィンドウ

これは、次のようにロードオンデマンドの処理を実行している場合、ひどく噛みつくことがあります。

private MyClass _myObj;
public MyClass MyObj {
  get {
    if (_myObj == null)
      _myObj = CreateMyObj(); // some other code to create my object
    return _myObj;
  }
}

ここで、これを使用している別のコードがあるとします。

// blah
// blah
MyObj.DoStuff(); // Line 3
// blah

次に、CreateMyObj()メソッドをデバッグします。したがって、コードにステップインするつもりで、上の行3にブレークポイントを配置します。ちょうどいいことですが、上の行にブレークポイントを設定し、_myObj = CreateMyObj();さらにその中にブレークポイントを設定しCreateMyObj()ます。

コードは3行目のブレークポイントにヒットします。コードにステップインします。_myObj明らかにnull なので、条件付きコードを入力することを期待していますか?ええと...それで...なぜ条件をスキップして直進したのreturn _myObjですか?マウスを_myObj ...の上に置くと、確かに値があります。どうしてこうなりました?!

答えは、「監視」ウィンドウが開いているため、IDEが値を取得したためです。特に、「自動」監視ウィンドウには、現在または前の実行行に関連するすべての変数/プロパティの値が表示されます。3行目でブレークポイントに到達したとき、ウォッチウィンドウは次の値を知りたいと思っていると判断しましたMyObj-舞台裏では、ブレークポイントを無視して、値を計算しましたMyObj-その呼び出しを含めCreateMyObj()て_myObjの値を設定します!

これが私がこれをハイゼンベルクウォッチウィンドウと呼んでいる理由です。影響を与えずに値を観察することはできません... :)

GOTCHA!


編集 -@ChristianHayterのコメントは、この問題の効果的な回避策のように見えるので、メインの回答に含めるに値すると思います。したがって、遅延ロードされたプロパティがあるときはいつでも...

[DebuggerBrowsable(DebuggerBrowsableState.Never)]または[DebuggerDisplay( "<ロードオンデマンド>")]でプロパティを装飾します。–クリスチャンヘイター


10
見事な発見!あなたはプログラマーではなく、あなたは本当のデバッガーです。
これ。__curious_geek

26
ウォッチウィンドウだけでなく、変数の上をホバリングしてもこれに遭遇しました。
Richard Morgan、

31
[DebuggerBrowsable(DebuggerBrowsableState.Never)]またはでプロパティを装飾します[DebuggerDisplay("<loaded on demand>")]
Christian Hayter 2013

4
フレームワーククラスを開発していて、遅延構築されたプロパティの実行時の動作を変更せずにウォッチウィンドウ機能が必要な場合は、デバッガータイプのプロキシを使用して、既に構築されている場合は値を返し、プロパティが設定されていないというメッセージを返すことができます。その場合は構築されています。Lazy<T>(そのための特定のクラスValueのプロパティ)は、これが使用される場合の一例です。
サムハーウェル2013年

4
(何らかの理由で理解できない)のオーバーロードでオブジェクトの値を変更した人を思い出しますToString。ツールヒントにカーソルを合わせるたびに、ツールチップによって異なる値が与えられました-彼はそれを理解できませんでした...
JNF

144

ここに私を捕まえる別の時があります:

static void PrintHowLong(DateTime a, DateTime b)
{
    TimeSpan span = a - b;
    Console.WriteLine(span.Seconds);        // WRONG!
    Console.WriteLine(span.TotalSeconds);   // RIGHT!
}

TimeSpan.Secondsは、タイムスパンの秒の部分です(2分0秒の秒の値は0です)。

TimeSpan.TotalSecondsは、秒単位で測定されるタイムスパン全体です(2分の合計秒の値は120です)。


1
ええ、私も持っています。TimeSpan.SecondsPartか、それが何を表しているかをより明確にするための何かである必要があると思います。
Dan Diplo、

3
これを読み直すと、なぜプロパティTimeSpanさえあるのか疑問に思う必要ありSecondsます。とにかく、誰がタイムスパンの秒の部分が何であるかをネズミのお尻に与えるのですか?これは、ユニットに依存する任意の値です。その実用的な使い方は思いつきません。
MusiGenesis 2010年

2
TimeSpan.TotalSecondsが返されることは私には理にかなっています...期間の合計秒数。
エドS.

16
@MusiGenesisプロパティは便利です。分割されたタイムスパンを表示したい場合はどうなりますか?たとえば、タイムスパンが「3時間15分10秒」の期間を表しているとします。秒、時間、分のプロパティなしでどのようにこの情報にアクセスできますか?
SolutionYogi、

1
同様のAPIでは、2つを区別するために使用SecondsPartSecondsTotalました。
BlueRaja-Danny Pflughoeft、2015年

80

イベントのフックを解除しなかったため、メモリがリークしています。

これは、私が知っている一部の上級開発者をも引き付けました。

たくさんのものが入っているWPFフォームを想像してみてください。そして、どこかでイベントにサブスクライブしています。購読を解除しないと、フォーム全体が閉じられ、参照が解除された後もメモリ内に保持されます。

私が見た問題は、WPFフォームでDispatchTimerを作成し、Tickイベントにサブスクライブしたことです。タイマーで-=を実行しないと、フォームでメモリリークが発生します。

この例では、ティアダウンコードは

timer.Tick -= TimerTickEventHandler;

WPFフォーム内にDispatchTimerのインスタンスを作成したため、これは特にトリッキーです。ガベージコレクションプロセスによって処理される内部参照であると思われます...残念ながら、DispatchTimerはサブスクリプションとサービスの静的内部リストを使用しますUIスレッドでリクエストを送信するため、参照は静的クラスによって「所有」されます。


1
秘訣は、作成したすべてのイベントサブスクリプションを常に解放することです。Formsがあなたに代わって頼りにし始めた場合、あなたはその習慣に確実に入ることができ、ある日、それを行う必要がある場所のどこかでイベントをリリースするのを忘れます。
Jason Williams

3
弱参照イベントのMS-接続の提案があり、ここで私の意見では私たちは完全にCABで使用されているように、弱結合された1つで信じられないほど貧弱なイベントモデルを置き換える必要がありますけれども、この問題を解決するだろう。
BlueRaja-Danny Pflughoeft 2010

+1、ありがとう!まあ、私がしなければならなかったコードレビュー作業に感謝しません!
ボブ・デニー

@ BlueRaja-DannyPflughoeft弱いイベントでは別の問題があります-ラムダをサブスクライブできません。あなたは書くことができませんtimer.Tick += (s, e,) => { Console.WriteLine(s); }
アークくん

@アークくんはいラムダはそれをさらに難しくします、ラムダを変数に保存し、それをティアダウンコードで使用する必要があります。ラムダを書くことの単純さを破壊するのではないですか?
ティモシーウォルターズ

63

動作はMSDNで明確に記述されているため、実際には問題ではないかもしれませんが、直感に反していることがわかったため、一度首を折れました。

Image image = System.Drawing.Image.FromFile("nice.pic");

この男は去ります "nice.pic"画像が破棄されるまでファイルをロックしたます。当時、私はそれに直面していましたが、その場でアイコンをロードするのは良いことですが、最初は何十もの開いていてロックされているファイルがあることに気づきませんでした。画像はどこからファイルをロードしたかを追跡します...

これを解決するには?ワンライナーで十分だと思いました。に追加のパラメーターを期待していましたが、パラメーターFromFile()がなかったため、これを記述しました...

using (Stream fs = new FileStream("nice.pic", FileMode.Open, FileAccess.Read))
{
    image = System.Drawing.Image.FromStream(fs);
}

10
この行動は意味をなさないことに同意します。「この動作は仕様です」以外の説明はありません。
MusiGenesis 2009年

1
ああ、この回避策の優れた点は、Image.ToStream(正確な名前を手元に忘れている)を後で呼び出そうとしても機能しないことです。
ジョシュア

55
コードを確認する必要があります。すぐに戻る。
Esben Skov Pedersen、2012

7
@EsbenSkovPedersenそのようなシンプルだが面白い&ドライなコメント。私の日を作りました。
イニシア2013

51

ASP.NETを数えるなら、Webフォームのライフサイクルは私にとってかなり大きな問題だと思います。多くの開発者がどのイベントハンドラーを使用するか(本当に残念なことに)をいつ使用するか本当に理解していないため、私は数え切れないほどの時間を費やしてWebフォームのコードをデバッグしました。


26
それが私がMVCに移動した理由です...ビューステートの頭痛の種...
chakrit

29
ASP.NETの問題(特に当然)に特化した他の完全な質問がありました。ASP.NETの基本的な概念(開発者にとってWebアプリをWindowsアプリのように見せること)は非常に誤解されているため、「おかしな」ものとして数えられるかどうかはわかりません。
MusiGenesis 2008年

1
MusiGenesis私はあなたのコメントに100回賛成票を投じることができればと思います。
csauve 2010

3
@MusiGenesis見当違いのようですが、当時、人々はWebアプリケーション(アプリケーションがキーワード-ASP.NET WebFormsはブログをホストするようには設計されていませんでした)がWindowsアプリケーションと同じように動作することを望んでいました。これは比較的最近になってようやく変化し、多くの人々はまだ「そこにはまったくいない」のです。全体的な問題は、抽象化があまりにも漏れていたということでした。Webはデスクトップアプリケーションのように動作せず、ほとんどすべての人に混乱をもたらしました。
Luaan 2014年

1
皮肉なことに、ASP.NETについて最初に目にしたのは、ASP.NETを使用してブログサイトを簡単に作成できることを示すマイクロソフトのビデオでした。
MusiGenesis 2014年

51

オーバーロードされた==演算子と型なしコンテナ(配列リスト、データセットなど):

string my = "my ";
Debug.Assert(my+"string" == "my string"); //true

var a = new ArrayList();
a.Add(my+"string");
a.Add("my string");

// uses ==(object) instead of ==(string)
Debug.Assert(a[1] == "my string"); // true, due to interning magic
Debug.Assert(a[0] == "my string"); // false

ソリューション?

  • string.Equals(a, b)文字列型を比較するときは常に使用します

  • ジェネリックList<string>を使用して、両方のオペランドが文字列であることを確認します。


6
そこに余分なスペースがあり、すべてが間違っています-スペースを削除しても、「my」+「string」は定数なので、最後の行はまだtrueです。
Jon Skeet、

1
ああ!そうです:)わかりました、私は少し編集しました。
ジミー、

このような用途では警告が生成されます。
chakrit 2008年

11
はい、C#言語の最大の欠陥の1つは、Objectクラスの==演算子です。彼らは私たちにReferenceEqualsを使わざるを得なかったはずです。
erikkallen 2009年

2
ありがたいことに、2.0以降、ジェネリックが使用されています。上記の例でArrayListの代わりにList <string>を使用している場合でも、心配する必要はありません。さらに、私たちはそれからパフォーマンスを得ました、そうです!私は常に、以前のコードでArrayListへの古い参照を根絶します。
JoelC 2014年

48
[Serializable]
class Hello
{
    readonly object accountsLock = new object();
}

//Do stuff to deserialize Hello with BinaryFormatter
//and now... accountsLock == null ;)

ストーリーのモラル:オブジェクトを逆シリアル化するときにフィールド初期化子が実行されない


8
はい、デフォルトのコンストラクタを実行していないため、.NETシリアル化は嫌いです。コンストラクタを呼び出さずにオブジェクトを構築することは不可能であると思いますが、そうではありません。
ロマン・スターコフ2011

45

DateTime.ToString( "dd / MM / yyyy") ; これは実際に常にdd / MM / yyyyを与えるわけではありませんが、代わりに地域設定を考慮し、現在の場所に応じて日付の区切り文字を置き換えます。そのため、dd-MM-yyyyまたは同様のものを取得する可能性があります。

これを行う正しい方法は、DateTime.ToString( "dd '/' MM '/' yyyy");を使用することです。


DateTime.ToString( "r")は、GMTを使用するRFC1123に変換することになっています。GMTはUTCから数分の1秒以内ですが、問題のDateTimeがLocalとして指定されている場合でも、「r」フォーマット指定子はUTC変換されません

その結果、次のような問題が発生します(現地時間とUTCの距離によって異なります)。

DateTime.Parse("Tue, 06 Sep 2011 16:35:12 GMT").ToString("r")
>              "Tue, 06 Sep 2011 17:35:12 GMT"

おっと!


19
mmをMMに変更-mmは分、MMは月。別の落とし穴、私は推測します...
コビ

1
あなたがそれを知らなかった場合、私はこれがどのように問題になるかを知ることができました(私は知りませんでした)...地域の設定と一致しません。
Beska

6
@Beska:ファイルに書き込むため、日付形式を指定した特定の形式にする必要があります。
GvS 2010年

11
ローカライズされているデフォルトは他の方法よりも悪いと私は考えています。少なくとも開発者はローカリゼーションを完全に無視しました。コードは異なる方法でローカライズされたマシンで機能します。この方法では、コードはおそらく機能しません。
ジョシュア

32
実際、これを行う正しい方法は次のようになると思いますDateTime.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture);
BlueRaja-Danny Pflughoeft

44

これは先日投稿されたのを見たけど、それはかなりあいまいで、知らない人にとっては苦痛だと思う

int x = 0;
x = x++;
return x;

それはほとんどが期待するような1ではなく0を返すので


37
それが実際に人々を噛まないことを願っています-そもそも彼らがそれを書いてくれないことを本当に望んでいます!(もちろん、とにかく興味深いです。)
Jon Skeet、

12
これはあまりわかりにくいとは思いません...
Chris Marasti-Georg、

10
少なくとも、C#では、予期しない場合は結果が定義されます。C ++では、0または1、またはプログラムの終了を含むその他の結果になる可能性があります。
James Curran

7
これは問題ではありません。x = x ++-> x = x、次にx .... x = ++ xをインクリメントし、xをインクリメントし、次にx = x
Kevin

28
@ケビン:そんなに簡単なことではないと思います。x = x ++がx = xの後にx ++が続く場合、結果はx = 1になります。代わりに、最初に等号の右側の式が評価されて(0が与えられて)、xはインクリメント(x = 1を与える)、そして最後に割り当てが実行されます(x = 0をもう一度与える)。
Tim Goodman

39

私はこのパーティーに少し遅れましたが、私は最近両方とも私を噛んだ2つの落とし穴があります:

日時解決

Ticksプロパティは、100万分の1秒(100ナノ秒ブロック)単位で時間を測定しますが、解像度は100ナノ秒ではなく、約15ミリ秒です。

このコード:

long now = DateTime.Now.Ticks;
for (int i = 0; i < 10; i++)
{
    System.Threading.Thread.Sleep(1);
    Console.WriteLine(DateTime.Now.Ticks - now);
}

(たとえば)の出力が得られます:

0
0
0
0
0
0
0
156254
156254
156254

同様に、DateTime.Now.Millisecondを見ると、15.625ミリ秒(15、31、46など)の丸められたチャンクで値を取得します。

この特定の動作はシステムによって異なりますが、この日付/時刻API には他の解決に関連する問題あります。


Path.Combine

ファイルパスを組み合わせる優れた方法ですが、常に期待どおりに動作するとは限りません。

2番目のパラメーターが\文字で始まる場合、完全なパスは提供されません。

このコード:

string prefix1 = "C:\\MyFolder\\MySubFolder";
string prefix2 = "C:\\MyFolder\\MySubFolder\\";
string suffix1 = "log\\";
string suffix2 = "\\log\\";

Console.WriteLine(Path.Combine(prefix1, suffix1));
Console.WriteLine(Path.Combine(prefix1, suffix2));
Console.WriteLine(Path.Combine(prefix2, suffix1));
Console.WriteLine(Path.Combine(prefix2, suffix2));

次の出力が得られます。

C:\MyFolder\MySubFolder\log\
\log\
C:\MyFolder\MySubFolder\log\
\log\

17
〜15ms間隔での時間の量子化は、基礎となるタイミングメカニズムの精度の欠如によるものではありません(これについては先ほど詳しく説明しませんでした)。これは、アプリがマルチタスクOS内で実行されているためです。Windowsは約15ミリ秒ごとにアプリにチェックインし、アプリが取得する小さなタイムスライスの間に、アプリは最後のスライス以降にキューに入れられたすべてのメッセージを処理します。そのスライス内のすべての呼び出しは、事実上まったく同時に行われるため、まったく同じ時間を返します。
MusiGenesis 2009

2
@MusiGenesis:私はそれがどのように機能するかを(今のところ)知っていますが、実際にはそれほど正確ではないような正確な測定をすることは私を誤解させるようです。これは、実際の高さを1千万単位に丸めているだけなのに、自分の身長をナノメートルで知っていると言っているようなものです。
Damovisa 2009

7
DateTimeは、最大で1ティックまで保存できます。それはその正確さを使用していないDateTime.Nowです。
ルーベン

16
余分な '\'は多くのunix / mac / linuxの人々にとって問題です。Windowsでは、先頭に「\」がある場合、それはドライブのルート(つまりC :)に移動したいということです。つまり、CDコマンドで試して、意味を確認してください。...1)移動C:\Windows\System322)タイプCD \Users3)すごい!今あなたはC:\Users... GOT ITにいますか?... Path.Combine(@ "C:\ Windows \ System32"、@ "\ Users")\Usersは正確に次を返すはずです[current_drive_here]:\Users
chakrit

8
「スリープ」がなくても、これは同じように実行されます。これは、15ミリ秒ごとにスケジュールされるアプリとは関係ありません。DateTime.UtcNowによって呼び出されるネイティブ関数GetSystemTimeAsFileTimeは、解像度が低いようです。
神保

38

コンソールに書き込むプロセスを(System.Diagnosticsを使用して)開始したが、Console.Outストリームを読み取らなかった場合、一定量の出力の後、アプリがハングしているように見えます。


3
stdoutとstderrの両方をリダイレクトし、2つのReadToEnd呼び出しを順番に使用しても、同じことが起こります。stdoutとstderrの両方を安全に処理するには、それぞれの読み取りスレッドを作成する必要があります。
Sebastiaan M

34

Linq-To-Sqlには演算子のショートカットはありません

こちらをご覧ください

つまり、Linq-To-Sqlクエリの条件句内では、||およびのような条件付きショートカットを使用して&&null参照例外を回避することはできません。Linq-To-Sqlは、最初の条件で2番目の条件を評価する必要がなくても、ORまたはAND演算子の両側を評価します。


8
TIL。BRB、数百のLINQクエリの再最適化...
tsilb '19

30

仮想メソッドでのデフォルトパラメータの使用

abstract class Base
{
    public virtual void foo(string s = "base") { Console.WriteLine("base " + s); }
}

class Derived : Base
{
    public override void foo(string s = "derived") { Console.WriteLine("derived " + s); }
}

...

Base b = new Derived();
b.foo();

出力:
派生ベース


10
奇妙なことに、これは完全に明白だと思いました。宣言されたタイプがの場合、Baseコンパイラはデフォルト値をBaseどこから取得する必要がありますか?私は少しだと思っているだろうより多くの宣言された型である場合は、デフォルト値が異なることができることを知っておかなけれ派生(静的)と呼ばれる方式が基本法であっても、タイプ。
ティムウィ

1
メソッドの1つの実装が別の実装のデフォルト値を取得するのはなぜですか?
staafl 2014年

1
@staaflデフォルトの引数は、実行時ではなくコンパイル時に解決されます。
fredoverflow 2014年

1
私はこの落とし穴が一般にデフォルトのパラメーターであると言います-多くの場合、実行時ではなくコンパイル時に解決されることを人々は認識していません。
Luaan 14年

4
@FredOverflow、私の質問は概念的なものでした。この動作は実装に関しては理にかなっていますが、直感的ではなく、エラーの原因となる可能性があります。C#コンパイラーは、オーバーライド時にデフォルトのパラメーター値を変更できないようにする必要があります。
staafl 2014年

27

変更可能なコレクションの値オブジェクト

struct Point { ... }
List<Point> mypoints = ...;

mypoints[i].x = 10;

効果はありません。

mypoints[i]Point値オブジェクトのコピーを返します。C#では、コピーのフィールドを変更できます。黙って何もしない。


更新: これはC#3.0で修正されたようです。

Cannot modify the return value of 'System.Collections.Generic.List<Foo>.this[int]' because it is not a variable

6
それが実際に配列で機能する(あなたの答えとは逆に)が、List <Point>などの他の動的コレクションでは機能しないことを考えると、それが混乱している理由がわかります。
Lasse V. Karlsen、

2
あなたが正しい。ありがとう。私は私の答えを修正しました:)。arr[i].attr=ライブラリコンテナーでコーディングできない配列の特別な構文です;(。なぜ(<値式>)。attr = <expr>が許可されているのですか?理にかなっていますか?
Bjarke Ebert

1
@Bjarke Ebert:意味のある場合もありますが、残念ながらコンパイラーがそれらを識別して許可する方法はありません。使用シナリオの例:正方形の2次元配列への参照と「回転/反転」インジケーターを保持する不変の構造体。構造体自体は不変であるため、読み取り専用インスタンスの要素への書き込みは問題ありませんが、コンパイラは、プロパティセッターが実際に構造体を書き込まないことを知らないため、許可されません。 。
スーパーキャット2012

25

おそらく最悪ではないかもしれませんが、.netフレームワークの一部は度使用していますが、他の部分はラジアンを 使用しています(そしてIntellisenseに表示されるドキュメントではどちらが表示されるかはわかりません。MSDNにアクセスして調べる必要があります)。

これはすべて、Angle代わりにクラスを用意することで回避できました...


私は、これは私の他の落とし穴は、これよりも大幅に悪化している考えると、非常に多くのupvotesを持って驚いた
BlueRaja -ダニーPflughoeft

22

C / C ++プログラマーにとって、C#への移行は自然なことです。ただし、私が個人的に遭遇した(そして他の人が同じ移行を行っているのを見た)最大の落とし穴は、C#のクラスと構造体の違いを完全に理解することではありません。

C ++では、クラスと構造体は同じです。デフォルトの可視性のみが異なり、クラスのデフォルトはプライベートな可視性、構造体のデフォルトはパブリックな可視性です。C ++では、このクラス定義

    class A
    {
    public:
        int i;
    };

機能的には、この構造体の定義と同等です。

    struct A
    {
        int i;
    };

ただし、C#では、クラスは参照型であり、構造体は値型です。これは作るビッグ(1)オブジェクトの等価性をテストする他の上の1つ、(2)を使用する場合を決定する、(3)の性能(例えば、ボクシング/アンボクシング)などの差を

両者の違いに関連するあらゆる種類の情報がウェブ上にあります(例:ここ)。C#への移行を行うすべての人に、少なくとも違いとその影響についての実用的な知識があることを強くお勧めします。


13
それで、最悪の落とし穴は、人々がそれを使う前に時間をかけて言語を学ぶことに煩わされていないことですか?
BlueRaja-ダニープフルフフト

3
@ BlueRaja-DannyPflughoeft明らかに類似した言語の古典的な落とし穴に似ています-それらは非常に類似したキーワードと多くの場合構文を使用しますが、多くの異なる方法で機能します。
Luaan 14年

19

ガベージコレクションとDispose()。メモリを解放するために何もする必要はありませんが、Dispose()を介してリソースを解放する必要があります。これは、WinFormsを使用しているとき、または何らかの方法でオブジェクトを追跡しているときに忘れることが非常に簡単なことです。


2
using()ブロックはこの問題をきれいに解決します。Disposeへの呼び出しを見つけたら、すぐに安全にリファクタリングしてusing()を使用できます。
ジェレミーフレイ

5
問題はIDisposableを正しく実装することだったと思います。
Mark Brackett

4
一方、PInvokeで作業しているときのように、using()の習慣は予期せずあなたに噛みつくことがあります。APIがまだ参照しているものを破棄したくない場合。
MusiGenesis 2008年

3
IDisposableを正しく実装することは非常に難しく、私がこれで見つけた最高のアドバイス(.NET Frameworkガイドライン)を理解するのも、最終的に「取得」するまで混乱する可能性があります。
2009年

1
私が今までIDisposableをで見つかった最高のアドバイスには、ステファン・クリアリーから来ている3つの簡単なルールIDisposableをに関する詳細な記事
ローマStarkov

19

配列の実装 IList

ただし、実装しないでください。Addを呼び出すと、機能しないことが通知されます。では、クラスがインターフェースをサポートできないのに、なぜインターフェースを実装するのでしょうか。

コンパイルしますが、機能しません。

IList<int> myList = new int[] { 1, 2, 4 };
myList.Add(5);

シリアライザー(WCF)がすべてのIListsを配列に変換し、ランタイムエラーが発生するため、この問題はたくさんあります。


8
私見、問題は、コレクションに定義された十分なインターフェイスがMicrosoftにないことです。私見、それはiEnumerable、iMultipassEnumerable(リセットをサポートし、複数のパスが一致することを保証します)、iLiveEnumerable(列挙中にコレクションが変更された場合、部分的に定義されたセマンティクスを持つ必要があります-変​​更が列挙に表示される場合と表示されない場合がありますが、原因はありません)偽の結果または例外)、iReadIndexable、iReadWriteIndexableなど。インターフェースは他のインターフェースを「継承」できるため、これによって余分な作業が追加されることはありません(NotImplementedスタブが保存されます)。
スーパーキャット

@supercat、それは初心者や特定の長時間のコーダーにとっては地獄のように混乱するでしょう。.NETコレクションとそのインターフェースは非常にエレガントだと思います。しかし、私はあなたの謙虚さに感謝します。;)
ヨルダン

@Jordan:上記を書いているので、私はより良いアプローチは、両方を持つことがあったであろうことを決めたIEnumerable<T>IEnumerator<T>サポートしFeatures、その有用性が報告「機能」何によって決定されるであろういくつかの「オプション」メソッドと同様の特性を。ただし、私は要点を説明します。つまり、aを受け取るコードIEnumerable<T>IEnumerable<T>は、提供するよりも強い約束が必要になる場合があります。電話をかけるとToListIEnumerable<T>そのような約束が守られますが、多くの場合、不必要に高価になります。私はあるはずだと
思い

...を受け取るコードが、必要に応じIEnumerable<T>てコンテンツのコピーを作成できるが、不必要にコピーを行わないようにすることができる手段。
スーパーキャット2015年

あなたのオプションは絶対に読めません。コードでIListを見ると、Featuresプロパティを調べる必要がなく、何を処理しているかがわかります。プログラマーは、コードの重要な機能が、コンピューターだけでなく、他の人にも読み取れることを忘れたいと思っています。.NETコレクションの名前空間は理想的ではありませんが、それは良いことであり、最良のソリューションを見つけることは、原則をより理想的に適合させることではありません。私がこれまでに作業した中で最悪のコードのいくつかは、DRYに理想的に適合しようとするコードでした。スクラップして書き直しました。それは単に悪いコードでした。私はあなたのフレームワークをまったく使いたくないと思います。
ジョーダン

18

foreachは変数スコープをループします!

var l = new List<Func<string>>();
var strings = new[] { "Lorem" , "ipsum", "dolor", "sit", "amet" };
foreach (var s in strings)
{
    l.Add(() => s);
}

foreach (var a in l)
    Console.WriteLine(a());

5つの「amet」を出力しますが、次の例は正常に機能します

var l = new List<Func<string>>();
var strings = new[] { "Lorem" , "ipsum", "dolor", "sit", "amet" };
foreach (var s in strings)
{
    var t = s;
    l.Add(() => t);
}

foreach (var a in l)
    Console.WriteLine(a());

11
これは本質的に、匿名メソッドを使用したJonの例と同等です。
Mehrdad Afshari、

3
「s」変数の方がスコープ付き変数と簡単に組み合わせることができるforeachとさらに混乱することを保存してください。一般的なforループでは、インデックス変数は明らかに各反復で同じです。
Mikko Rantanen、

2
blogs.msdn.com/ericlippert/archive/2009/11/12/…そしてはい、変数が「適切に」スコープされていることを望みます。
Roman Starkov、2010年


基本的に、同じ変数を変更せずに何度も繰り返し出力するだけです。
ジョーダン

18

MS SQL Serverは1753より前の日付を処理できません。重要なことにDateTime.MinDate、これは.NET 定数(1/1/1)と同期していません。したがって、気がかりな日付、不正な日付(最近、データインポートで起こったようなもの)、または単にウィリアム征服王の誕生日を保存しようとすると、問題が発生します。このための組み込みの回避策はありません。1753年より前の日付で作業する必要がある場合は、独自の回避策を作成する必要があります。


17
率直に言って、MS SQL Serverにはこの権利があり、.Netは間違っていると思います。調査を行うと、カレンダーの変更、完全にスキップされた日などが原因で、1751年より前の日付がファンキーになることがわかります。ほとんどのRDBMには、いくつかのカットオフポイントがあります。これが出発点になるはずです:ancestry.com/learn/library/article.aspx
article

11
また、日付は1753です。これは、日付をスキップせずに連続カレンダーを使用するのが初めてでした。SQL 2008では、Dateとdatetime2のdatetypeが導入され、1/1/01から12/31/9999までの日付を受け入れることができます。ただし、実際に1753年より前の日付を比較している場合は、これらの型を使用した日付の比較を疑って表示する必要があります。
NotMe 2009年

ああ、そうです、1753年、修正、ありがとう。
Shaul Behr、

そのような日付と日付比較を行うことは本当に意味がありますか?つまり、History Channelにとって、これは理にかなっていますが、アメリカが発見された正確な曜日を知りたがっていません。
Camilo Martin

5
ユリウス日のウィキペディアを介して、うるう日と1753年のスキップ日を考慮して、紀元前約5000年まで日付計算を行うことができる1984年に発行された13行の基本プログラムCALJD.BASを見つけることができます。 "SQL2008のようなシステムは、もっと悪くなるはずです。15世紀の正しい日付表現に興味がないかもしれませんが、他の人は興味があるかもしれません。私たちのソフトウェアはこれをバグなしで処理する必要があります。もう1つの問題はうるう秒です。。。
Roland

18

厄介なLinq Caching Gotcha

この発見につながった私の質問と、問題を発見したブロガーを参照しください。

つまり、DataContextは、これまでにロードしたすべてのLinq-to-Sqlオブジェクトのキャッシュを保持します。以前にロードしたレコードを他の誰かが変更した場合、レコードを明示的に再ロードしても、最新のデータを取得できません

これはObjectTrackingEnabled、デフォルトではtrueであるDataContextで呼び出されるプロパティが原因です。あなたがfalseにそのプロパティを設定した場合、レコードは改めてたびにロードされます... しかし、あなたがでSubmitChangesとそのレコードへの変更を永続化することはできません...()。

GOTCHA!


Ivはこのバグを追いかけて1日半(と髪の毛!)を追いかけました...
外科コーダー

これは並行性の競合と呼ばれ、多少の強引な傾向があるものの、現在、これを回避する方法はいくつかありますが、現在でも問題があります。DataContextは悪夢でした。O_o
ジョーダン

17

Stream.Readの契約は、私が多くの人をつまずかせるのを見てきました。

// Read 8 bytes and turn them into a ulong
byte[] data = new byte[8];
stream.Read(data, 0, 8); // <-- WRONG!
ulong data = BitConverter.ToUInt64(data);

これは間違っている理由は、それがあるStream.Read読み込みます、最大で指定されたバイト数を、しかしである、完全無料の別の7つのバイトは、ストリームの終了前に利用可能である場合でも、ちょうど1バイトを読み取ります。

それはに非常に類似したこのルックスことを助けないStream.Writeされ、それは例外なく返した場合の保証は、すべてのバイトを書かれているし。また、上記のコードがほとんど常に機能することも役に立ちません。そしてもちろん、正確にNバイトを正確に読み取るための既製の便利な方法がないことは役に立ちません。

そのため、穴を塞ぎ、これに対する認識を高めるために、これを行う正しい方法の例を次に示します。

    /// <summary>
    /// Attempts to fill the buffer with the specified number of bytes from the
    /// stream. If there are fewer bytes left in the stream than requested then
    /// all available bytes will be read into the buffer.
    /// </summary>
    /// <param name="stream">Stream to read from.</param>
    /// <param name="buffer">Buffer to write the bytes to.</param>
    /// <param name="offset">Offset at which to write the first byte read from
    ///                      the stream.</param>
    /// <param name="length">Number of bytes to read from the stream.</param>
    /// <returns>Number of bytes read from the stream into buffer. This may be
    ///          less than requested, but only if the stream ended before the
    ///          required number of bytes were read.</returns>
    public static int FillBuffer(this Stream stream,
                                 byte[] buffer, int offset, int length)
    {
        int totalRead = 0;
        while (length > 0)
        {
            var read = stream.Read(buffer, offset, length);
            if (read == 0)
                return totalRead;
            offset += read;
            length -= read;
            totalRead += read;
        }
        return totalRead;
    }

    /// <summary>
    /// Attempts to read the specified number of bytes from the stream. If
    /// there are fewer bytes left before the end of the stream, a shorter
    /// (possibly empty) array is returned.
    /// </summary>
    /// <param name="stream">Stream to read from.</param>
    /// <param name="length">Number of bytes to read from the stream.</param>
    public static byte[] Read(this Stream stream, int length)
    {
        byte[] buf = new byte[length];
        int read = stream.FillBuffer(buf, 0, length);
        if (read < length)
            Array.Resize(ref buf, read);
        return buf;
    }

1
または、明示的な例では:var r = new BinaryReader(stream); ulong data = r.ReadUInt64();。BinaryReaderにもFillBufferメソッドがあります...
jimbobmcgee 2014年

15

イベント

イベントが言語機能である理由を理解できませんでした。それらは使い方が複雑です。呼び出す前にnullを確認する必要があり、登録を解除する必要があります(自分自身)。なぜイベントがライブラリのクラスではないのですか?基本的には専門List<delegate>ですか?


1
また、マルチスレッドは苦痛です。これらすべての問題を除いて、nullのものはCABで修正されます(その機能は実際には言語に組み込まれているだけです)。イベントはグローバルに宣言され、メソッドはそれ自体をイベントの「サブスクライバー」として宣言できます。CABに関する私の唯一の問題は、グローバルイベント名が列挙型ではなく文字列であることです(これは、本質的に文字列として機能するJavaのように、よりインテリジェントな列挙型で修正できます)。CABのセットアップは困難ですが、ここでは簡単なオープンソースのクローンを入手できます
BlueRaja-ダニープフルフフト

3
.netイベントの実装が嫌いです。イベントサブスクリプションは、サブスクリプションを追加し、Disposeされるとサブスクリプションを削除するIDisposableを返すメソッドを呼び出すことによって処理する必要があります。特に「デリゲート」を追加して後でマルチキャストデリゲートを削除しようとする場合(たとえば、「B」を追加してから「AB」を削除してから削除する場合) ( "BA"を残して) "B"と"AB"(依然として"BA"を残す)おっと。。
supercat

@supercatどのように書き直しbutton.Click += (s, e) => { Console.WriteLine(s); }ますか?
アークくん

他のイベントとは別にIEventSubscription clickSubscription = button.SubscribeClick((s,e)=>{Console.WriteLine(s);});登録を解除し、を介して登録解除できるようにする必要がある場合clickSubscription.Dispose();。私のオブジェクトは、その生涯を通じてすべてのサブスクリプションを維持したい場合は、MySubscriptions.Add(button.SubscribeClick((s,e)=>{Console.WriteLine(s);}));その後、MySubscriptions.Dispose()すべてのサブスクリプションを殺すために。
スーパーキャット2013年

@アークくん:サブスクリプションの外部をカプセル化するオブジェクトを保持しなければならないのは厄介に思えるかもしれませんが、サブスクリプションをエンティティと見なすと、それらをすべて確実にクリーンアップできるタイプに集約することができます。
スーパーキャット2013年

14

今日、私は長い間回避されていたバグを修正しました。このバグは、マルチスレッドシナリオで使用されているジェネリッククラスにあり、Interlockedを使用してロックのない同期を提供するために静的intフィールドが使用されていました。型の総称クラスのインスタンス化ごとに独自の静的があるため、バグが発生しました。したがって、各スレッドは独自の静的フィールドを取得し、意図したとおりにロックを使用していませんでした。

class SomeGeneric<T>
{
    public static int i = 0;
}

class Test
{
    public static void main(string[] args)
    {
        SomeGeneric<int>.i = 5;
        SomeGeneric<string>.i = 10;
        Console.WriteLine(SomeGeneric<int>.i);
        Console.WriteLine(SomeGeneric<string>.i);
        Console.WriteLine(SomeGeneric<int>.i);
    }
}

これは5 10 5を印刷します


5
静的を定義し、そこからジェネリックを継承する非ジェネリックの基本クラスを持つことができます。私はC#でこの動作に陥ることはありませんでしたが、一部のC ++テンプレートの長いデバッグ時間を覚えています... Eww!:)
パウリウス

7
奇妙なことに、これは明白だと思いました。iタイプがあった場合にどうするかを考えてくださいT
Timwi、2011

1
typeパラメータはの一部ですTypeSomeGeneric<int>とは異なるタイプSomeGeneric<string>です。そう、もちろん、それぞれが独自の持っているpublic static int i
radarbob

13

列挙型は複数回評価できます

遅延列挙型の列挙可能オブジェクトがあり、それを2回繰り返して異なる結果が得られると、噛み付くでしょう。(または同じ結果が得られますが、不必要に2回実行されます)

たとえば、特定のテストを作成しているときに、ロジックをテストするためにいくつかの一時ファイルが必要でした。

var files = Enumerable.Range(0, 5)
    .Select(i => Path.GetTempFileName());

foreach (var file in files)
    File.WriteAllText(file, "HELLO WORLD!");

/* ... many lines of codes later ... */

foreach (var file in files)
    File.Delete(file);

File.Delete(file)投げるときの驚きを想像してみてくださいFileNotFound!!

何ここで起こってはということですfiles列挙GOTは、反復二回(最初の反復の結果を単純にされていない覚えて)、あなたが再呼び出しされると思い、新しい各反復でPath.GetTempFilename()あなたは一時ファイル名の異なるセットを買ってあげるようにします。

解決策はもちろん、ToArray()またはを使用して値を熱心に列挙することですToList()

var files = Enumerable.Range(0, 5)
    .Select(i => Path.GetTempFileName())
    .ToArray();

次のようなマルチスレッドを実行している場合、これはさらに恐ろしいことです。

foreach (var file in files)
    content = content + File.ReadAllText(file);

そして、あなたcontent.Lengthはすべての書き込みの後にまだ0であることがわかります!! 次に、競合状態がないことを厳密にチェックし始めます。1時間無駄にした後...それは、あなたが忘れた小さな小さな列挙可能なものだけであることがわかりました。


これは仕様によるものです。これは遅延実行と呼ばれます。とりわけ、これはTSQL構造をシミュレートすることを目的としています。SQLビューから選択するたびに、異なる結果が得られます。また、SQL Serverなどのリモートデータストアに役立つチェーンも可能です。そうでない場合、x.Select.Where.OrderByは3つの個別のコマンドをデータベースに送信します...
as9876

@AYS質問のタイトルにある「Gotcha」という単語を見逃しましたか?
chakrit 2015

落とし穴はデザイナーの監督であり、意図的なものではないと思いました。
as9876 2015

たぶん、再起動できないIEnumerableには別のタイプがあるはずです。AutoBufferedEnumerable?簡単に実装できます。この落とし穴は、主にプログラマーの知識不足によるものと思われます。現在の動作に問題はないと思います。
Eldritch Conundrum 2016年

13

しばらく私をデバッグさせてしまった奇妙なものを見つけました:

例外をスローせずにnull可能なintのnullをインクリメントしても、値はnullのままです。

int? i = null;
i++; // I would have expected an exception but runs fine and stays as null

これが、C#がnull許容型の操作をどのように活用するかの結果です。それはあなたがそれに投げるすべてを消費するNaNに少し似ています。
IllidanS4がモニカに2016

10
TextInfo textInfo = Thread.CurrentThread.CurrentCulture.TextInfo;

textInfo.ToTitleCase("hello world!"); //Returns "Hello World!"
textInfo.ToTitleCase("hElLo WoRld!"); //Returns "Hello World!"
textInfo.ToTitleCase("Hello World!"); //Returns "Hello World!"
textInfo.ToTitleCase("HELLO WORLD!"); //Returns "HELLO WORLD!"

はい、この動作は文書化されていますが、それで間違いなく正しくなるわけではありません。


5
同意しない-単語がすべて大文字の場合、タイトルケースをめちゃくちゃにしたくないという特別な意味を持つ可能性があります。たとえば、「President of the USA」->「President Of the USA」ではなく、米国"。
Shaul Behr

5
@Shaul:で、その場合、彼らはこれを指定する必要があり、パラメータ -このなります私は事前にこの動作を期待誰も会ったことがありませんので、回避の混乱に知っておかなけれを
BlueRaja-Danny Pflughoeft
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.