C#または.NETで見た最も奇妙なコーナーケースは何ですか?[閉まっている]


322

私はいくつかのコーナーケースと頭の体操を集めており、いつももっと聞きたいです。このページでは、C#言語のビットとボブだけを取り上げていますが、.NETのコアなことも興味深いものです。たとえば、こちらはページにはありませんが、信じられないようなものです。

string x = new string(new char[0]);
string y = new string(new char[0]);
Console.WriteLine(object.ReferenceEquals(x, y));

私はそれがFalseを出力することを期待します-結局のところ、「新規」(参照タイプを伴う)は常に新しいオブジェクトを作成しますね?C#とCLIの両方の仕様は、そうすべきであると示しています。まあ、この特定のケースではありません。Trueを出力し、私がテストしたフレームワークのすべてのバージョンで実行しました。(確かに私はMonoで試したことはありません...)

明確にするために、これは私が探している種類の例にすぎません。この奇妙な点についての議論や説明は特に探していませんでした。(通常の文字列インターンと同じではありません。特に、コンストラクターが呼び出されたときに文字列インターンは通常発生しません。)私は、同様の奇妙な動作を本当に求めていました。

そこに潜んでいる他の宝石はありますか?


64
Mono 2.0 rcでテスト済み。Trueを返します
マークグラベル

10
どちらの文字列もstring.Emptyになり、フレームワークはそれへの参照を1つだけ保持しているように見えます
Adrian Zanescu

34
それは記憶保護のことです。静的メソッドstring.InternのMSDNドキュメントを参照してください。CLRは文字列プールを維持します。これが、同じ内容の文字列が同じメモリ、つまりオブジェクトへの参照として表示される理由です。
ジョンライデグレン、2009

12
@John:文字列のインターンは、リテラルに対してのみ自動的に行われます。ここではそうではありません。@DanielSwe:文字列を不変にするためにインターンは必要ありません。それが可能であるという事実は、不変性の当然の帰結ですが、通常のインターンはここではとにかく起こっていません。
ジョンスキート

3
この動作を引き起こす実装の詳細は、ここで説明されています:blog.liranchen.com/2010/08/brain-teasing-with-strings.html
Liran

回答:


394

私は以前にこれをお見せしたと思いますが、私はここの楽しみが好きです-これは追跡するためにデバッグが必要でした!(元のコードは明らかにより複雑で微妙でした...)

    static void Foo<T>() where T : new()
    {
        T t = new T();
        Console.WriteLine(t.ToString()); // works fine
        Console.WriteLine(t.GetHashCode()); // works fine
        Console.WriteLine(t.Equals(t)); // works fine

        // so it looks like an object and smells like an object...

        // but this throws a NullReferenceException...
        Console.WriteLine(t.GetType());
    }

それで、Tは何でしたか...

回答:任意Nullable<T>-などint?。取得できないGetType()を除いて、すべてのメソッドがオーバーライドされます。したがって、オブジェクトにキャスト(ボックス化)され(したがってnullに)、object.GetType()...を呼び出してnullを呼び出します;-p


アップデート:プロット濃く... Ayende Rahienが投げた彼のブログ上で同様の課題を、しかしとwhere T : class, new()

private static void Main() {
    CanThisHappen<MyFunnyType>();
}

public static void CanThisHappen<T>() where T : class, new() {
    var instance = new T(); // new() on a ref-type; should be non-null, then
    Debug.Assert(instance != null, "How did we break the CLR?");
}

しかし、それは敗北することができます!リモーティングなどで使用されるのと同じ間接参照を使用します。警告-以下は純粋な悪です:

class MyFunnyProxyAttribute : ProxyAttribute {
    public override MarshalByRefObject CreateInstance(Type serverType) {
        return null;
    }
}
[MyFunnyProxy]
class MyFunnyType : ContextBoundObject { }

これを実行するnew()と、呼び出しはプロキシ(MyFunnyProxyAttribute)にリダイレクトされ、プロキシが戻りますnull。さあ、目を洗ってください!


9
Nullable <T> .GetType()を定義できないのはなぜですか?結果はtypeof(Nullable <T>)であるべきではありませんか?
Drew Noakes、

69
ドリュー:問題は、GetType()が仮想ではないため、オーバーライドされないことです。つまり、メソッド呼び出し用に値がボックス化されます。ボックスはnull参照になるため、NREになります。
Jon Skeet、

10
@ドリュー; さらに、Nullable <T>には特別なボクシングルールがあります。つまり、空のNullable <T>はnullにボックス化され、空のNullable <T>を含むボックスではありません(空のNullable <T>にボックス化されません)。 >)
マークグラベル

29
とても、とてもクールです。冷静な方法で。;-)
Konrad Rudolph

6
コンストラクタの制約、C#3.0言語仕様の10.1.5
Marc Gravell

216

銀行家の丸め。

これはそれほどコンパイラのバグや誤動作ではありませんが、奇妙なコーナーケースです...

.Net Frameworkは、バンカーの丸めと呼ばれるスキームまたは丸めを採用しています。

Bankers 'Roundingでは、0.5の数値は最も近い偶数に丸められるため、

Math.Round(-0.5) == 0
Math.Round(0.5) == 0
Math.Round(1.5) == 2
Math.Round(2.5) == 2
etc...

これにより、よく知られた端数切り上げに基づく財務計算で予期しないバグが発生する可能性があります。

これはVisual Basicにも当てはまります。


22
私にも奇妙に思えた。つまり、少なくとも、数値の大きなリストを丸めて合計を計算するまではです。次に、単純に切り上げた場合、丸められていない数値の合計とは大きく異なる可能性があることに気付きます。財務計算をしている場合は非常に悪いです!
Tsvetomir Tsonev 2008年

255
人々が知らなかった場合は、次のことができます。Math.Round(x、MidpointRounding.AwayFromZero); 丸め方式を変更します。
ICR

26
ドキュメントから:このメソッドの動作はIEEE標準754、セクション4に従います。この種の丸めは、最も近い値への丸め、または銀行の丸めと呼ばれることがあります。これは、中間点の値を一方向に一貫して丸めることから生じる丸め誤差を最小限に抑えます。
ICR

8
これがint(fVal + 0.5)、丸め関数が組み込まれている言語でも頻繁に見られる理由なのでしょうか。
ベンブランク、

32
皮肉なことに、私はで働いていた銀行一度や他のプログラマはこのことについてアウトフリップ始め、丸めは枠組みの中で壊れていたことを考える
ダン・

176

Rec(0)(デバッガの下ではなく)として呼び出された場合、この関数は何をしますか?

static void Rec(int i)
{
    Console.WriteLine(i);
    if (i < int.MaxValue)
    {
        Rec(i + 1);
    }
}

回答:

  • 32ビットJITでは、StackOverflowExceptionが発生します。
  • 64ビットJITでは、すべての数値をint.MaxValueに出力する必要があります

これは、64ビットJITコンパイラが末尾呼び出しの最適化を適用するためですに対し、32ビットJITは適用しないためです。

残念ながら、これを検証するための64ビットマシンはありませんが、この方法は末尾呼び出しの最適化のすべての条件を満たしています。誰かが持っている場合は、それが本当かどうかを確認したいと思います。


10
リリースモードでコンパイルする必要がありますが、間違いなくx64で機能します=)
Neil Williams

3
現在のすべてのJITはリリースモードでTCOを実行するため、VS 2010が出たときに回答を更新する価値があるかもしれません
ShuggyCoUk

3
32ビットWinXPのVS2010 Beta 1で試してみました。それでもStackOverflowExceptionが発生します。
squillman

130
StackOverflowExceptionの+1
calvinlough

7
それ++は完全に私がオフに投げました。Rec(i + 1)普通の人のように呼べませんか?
コンフィギュレー

111

これを割り当て!


これは私がパーティーで聞きたいものです(おそらく私がもう招待されない理由です):

次のコードをコンパイルできますか?

    public void Foo()
    {
        this = new Teaser();
    }

簡単なチートは次のようになります:

string cheat = @"
    public void Foo()
    {
        this = new Teaser();
    }
";

しかし、本当の解決策はこれです:

public struct Teaser
{
    public void Foo()
    {
        this = new Teaser();
    }
}

したがって、値の型(構造体)がthis変数を再割り当てできることは、ほとんどわかっていません。


3
C ++クラスもそれを行うことができます...私が少し最近発見したように、最適化のために実際にそれを使用しようと試みたことに対して怒鳴られるだけです:p
mpen

1
実は新品のインプレースを使っていました。すべてのフィールドを更新する効率的な方法が欲しかっただけです:)
mpen

70
これもチートです://this = new Teaser();:-)
AndrewJacksonZA

17
:-)私は、この再割り当ての嫌悪よりも、プロダクションコードでこれらのチートを好むでしょう...
Omer Mor

2
CLRからC#を介して:別のコンストラクターで構造体のパラメーターなしのコンストラクターを呼び出すことができるためです。構造体の1つの値のみを初期化し、他の値をゼロ/ null(デフォルト)にしたい場合は、と書くことができますpublic Foo(int bar){this = new Foo(); specialVar = bar;}。これは効率的ではなく、実際には正当化されません(specialVar2回割り当てられます)が、FYIだけです。(それが本で与えられた理由です、なぜ私たちが単にすべきでないのか分かりませんpublic Foo(int bar) : this()
kizzx2

100

数年前、忠誠プログラムに取り組んでいたときに、顧客に与えられるポイントの量に問題がありました。この問題は、doubleをintにキャスト/変換することに関連していました。

以下のコードで:

double d = 13.6;

int i1 = Convert.ToInt32(d);
int i2 = (int)d;

i1 == i2ですか?

i1!= i2であることがわかります。変換およびキャスト演算子の丸めポリシーが異なるため、実際の値は次のとおりです。

i1 == 14
i2 == 13

Math.Ceiling()またはMath.Floor()(または、要件を満たすMidpointRoundingを持つMath.Round)を呼び出す方が常に良いです。

int i1 = Convert.ToInt32( Math.Ceiling(d) );
int i2 = (int) Math.Ceiling(d);

44
整数にキャストしても丸められません。切り捨てられるだけです(実質的には常に切り捨てられます)。したがって、これは完全に理にかなっています。
Max Schmeling、

57
@Max:はい、でも変換はなぜラウンドするのですか?
Stefan Steinegger、2009年

18
@Stefan Steineggerもしそれがすべてキャストされたとしたら、そもそも理由はないでしょうね。また、クラス名はCastではなくConvertであることにも注意してください。
バグロット

3
VBの場合:CInt()が丸められます。Fix()は切り捨てます。私を燃やし、一度(blog.wassupy.com/2006/01/i-can-believe-it-not-truncating.html
マイケル・ハーレン

74

enum関数のオーバーロードがある場合でも、0を整数にする必要があります。

0を列挙型にマッピングするためのC#コアチームの根拠は知っていましたが、それでも、必要なほど直角ではありません。Npgsqlの例。

テスト例:

namespace Craft
{
    enum Symbol { Alpha = 1, Beta = 2, Gamma = 3, Delta = 4 };


   class Mate
    {
        static void Main(string[] args)
        {

            JustTest(Symbol.Alpha); // enum
            JustTest(0); // why enum
            JustTest((int)0); // why still enum

            int i = 0;

            JustTest(Convert.ToInt32(0)); // have to use Convert.ToInt32 to convince the compiler to make the call site use the object version

            JustTest(i); // it's ok from down here and below
            JustTest(1);
            JustTest("string");
            JustTest(Guid.NewGuid());
            JustTest(new DataTable());

            Console.ReadLine();
        }

        static void JustTest(Symbol a)
        {
            Console.WriteLine("Enum");
        }

        static void JustTest(object o)
        {
            Console.WriteLine("Object");
        }
    }
}

18
うわーそれは私にとって新しいものです。また、ConverTo.ToIn32()がどのように機能するかについては奇妙ですが、(int)0へのキャストは機能しません。そして、0より大きい他の数は機能します。(「機能する」とは、オブジェクトをオーバーロードと呼ぶことを意味します。)
Lucas

この現象を回避グッドプラクティスを施行することをお勧めコード解析ルールがあります: msdn.microsoft.com/en-us/library/ms182149%28VS.80%29.aspx リンクはまた、どのように0マッピング作品の素敵な記述が含まれていること。
Chris Clark、

1
@Chris Clark:列挙型シンボルにNone = 0を付けてみました。それでもコンパイラは0とさらに(int)0の列挙型を選択します
Michael Buen

2
IMO彼らはnone、任意の列挙型に変換して使用できるキーワードを導入し、0を常にintにし、暗黙的に列挙型に変換できないようにする必要がありました。
CodesInChaos 2010年

5
結果はコンパイル時定数ではないため、ConverTo.ToIn32()は機能します。そして、コンパイル時定数0だけが列挙型に変換可能です。以前のバージョンの.netでは、リテラルのみ0を列挙型に変換できました。Eric Lippertのブログを参照してください:blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspx
CodesInChaos

67

これは、これまでに見た中で最も珍しいものの1つです(もちろん、ここのものは別として!):

public class Turtle<T> where T : Turtle<T>
{
}

宣言することはできますが、実際に使用することはありません。これは、中心にあるクラスを別のTurtleでラップするように常に要求するためです。

[ジョーク]カメはずっと下にいると思います... [/ジョーク]


34
:あなたはかかわらず、インスタンスを作成することができますclass RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
マルクGravell

24
確かに。これは、Java enumが非常に効果的に使用するパターンです。プロトコルバッファーでも使用します。
Jon Skeet、

6
RCIX、そうそうそうです。
ジョシュア

8
私はこのパターンを派手なジェネリックス関連でかなり頻繁に使用しました。正しく型付けされたクローンのようなもの、またはそれ自身のインスタンスの作成を可能にします。
Lucero、

20
これは「妙テンプレートパターンを繰り返し」ですen.wikipedia.org/wiki/Curiously_recurring_template_pattern
porges

65

これは私が最近知っただけのものです...

interface IFoo
{
   string Message {get;}
}
...
IFoo obj = new IFoo("abc");
Console.WriteLine(obj.Message);

上記は一見奇妙に見えますが、実際には合法です。いいえ、本当に(私は重要な部分を見逃しましたが、「クラスを追加する」や「エイリアスをポイントに追加する」などのハックではありません。IFoousingIFoo Aでクラス")。

その理由を理解できるかどうかを確認してください。インターフェイスをインスタンス化できないと誰が言うのでしょうか。


1
「エイリアスの使用」の+1-それができるとは知りませんでし
デビッド

COM相互運用機能のコンパイラをハッキングする:-)
Ion Todirel

この野郎!あなたは少なくとも「特定の状況下で」と言ったかもしれません...私のコンパイラは反証します!
MAハニン

56

ブール値がTrueでもFalseでもないのはいつですか?

Billは、AがTrueでBがTrueの場合、(AおよびB)がFalseになるようにブール値をハッキングできることを発見しました。

ハッキングされたブール値


134
もちろんFILE_NOT_FOUNDだと!
グレッグ、

12
これは興味深いことです。これは、数学的に言えば、C#のステートメントは証明できないことを意味します。おっと。
Simon Johnson、

20
いつか私はこの振る舞いに依存するプログラムを書くでしょう、そして最も暗い地獄の悪魔は私のために歓迎を準備します。ブハハハハハハ!
Jeffrey L Whitledge 09年

18
この例では、論理演算子ではなくビット単位を使用しています。それはどのように意外ですか?
ジョシュ・リー

6
まあ、彼は構造体のレイアウトをハックします、もちろんあなたは奇妙な結果を得るでしょう、これはそれほど驚くべきことでも予期せぬことでもありません!
Ion Todirel、2010年

47

パーティーに少し遅れて到着しましたが、3 つ4つ 5つあります。

  1. ロードまたは表示されていないコントロールでInvokeRequiredをポーリングすると、falseと表示され、別のスレッドから変更しようとすると顔が爆破されます(解決策はこれを参照することです)。コントロール)。

  2. 私をつまずかせたもう1つは、

    enum MyEnum
    {
        Red,
        Blue,
    }

    MyEnum.Red.ToString()を別のアセンブリで計算し、その間に誰かがあなたの列挙型を次のように再コンパイルした場合:

    enum MyEnum
    {
        Black,
        Red,
        Blue,
    }

    実行時には、「黒」が表示されます。

  3. 私はいくつかの便利な定数を含む共有アセンブリを使用していました。前任者は見苦しい外観のget-onlyプロパティをたくさん残していたので、煩雑さを取り除き、public constを使用すると思いました。VSが参照ではなくそれらの値にそれらをコンパイルしたとき、私は少し驚きました。

  4. あなたが別のアセンブリからインターフェイスの新しいメソッドを実装していますが、そのアセンブリの古いバージョンを参照し再構築する場合、あなたがいても、(「NewMethod」のない実装)TypeLoadExceptionを取得していないしている(参照、それを実装し、ここ)。

  5. 辞書<、>: "アイテムが返される順序は未定義です。" これは恐ろしいことです。時々噛まれることもありますが、他の人に働きかけることができます。そして、やみくもに辞書がうまくいくと思い込んだだけの場合(「なぜそうすべきではないのでしょうか。私は、リストがそうです」)、本当にあなたがついにあなたの仮定に疑問を持ち始める前にあなたの鼻をそれに入れてください。


6
#2は興味深い例です。列挙型は、整数値へのコンパイラマッピングです。したがって、これらの値を明示的に割り当てなかった場合でも、コンパイラーによって割り当てられ、MyEnum.Red = 0およびMyEnum.Blue = 1になります。Blackを追加すると、値0が再定義されてRedからBlackにマップされます。この問題は、シリアライゼーションなどの他の使用法でも明らかになったと思います。
LBushkin 2009年

3
呼び出しには+1が必要です。私たちでは、Red = 1、Blue = 2のような列挙型に値を明示的に割り当てることを好むので、常に同じ値になる前または後に新しい値を挿入できます。値をデータベースに保存する場合は特に必要です。
TheVillageIdiot 2009年

53
#5が「エッジケース」であることに同意しません。ディクショナリには、値をいつ挿入するかに基づいて定義された順序があってはなりません。定義された順序が必要な場合は、リストを使用するか、便利な方法でソートできるキーを使用するか、まったく異なるデータ構造を使用します。
ウェッジ、

21
@ Wedge、SortedDictionaryのようですか?
Allon Guralnek、2009

4
#3は、定数が使用されているすべての場所に定数が挿入されているために発生します(少なくともC#では)。前任者はすでにそれに気づいているかもしれません。それが彼らがget-onlyプロパティを使用した理由です。ただし、読み取り専用変数(constとは対照的)も同様に機能します。
Remoun 2009

33

VB.NET、nullable、および三項演算子:

Dim i As Integer? = If(True, Nothing, 5)

iを含めることを期待していたため、デバッグに時間がかかりましたNothing

私は本当に何を含んでいますか?0

これは驚くべきことですが、実際には「正しい」動作ですNothing。VB.NETの場合null、CLRの場合とまったく同じではありません。コンテキストに応じて、値の型をNothing意味するnull場合もあれdefault(T)ば、値の場合Tもあります。上記の場合、およびの一般的なタイプとしてIf推論Integerします。したがって、この場合、はを意味します。Nothing5Nothing0


興味深いことに、私はこの答えを見つけることができなかったため、質問を作成する必要がありまし。さて、答えはこのスレッドにあることを誰が知っていましたか?
GSerg 2011年

28

ロングショットで最初のコーナーケースに勝る2番目の本当に奇妙なコーナーケースを見つけました。

String.Equalsメソッド(String、String、StringComparison)は、実際には副作用がありません。

私はそれをいくつかの関数の先頭にある行に単独で持っているコードのブロックに取り組んでいました:

stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);

その行を削除すると、プログラムの他の場所でスタックオーバーフローが発生します。

そのコードは、BeforeAssemblyLoadイベントに本質的に対応するハンドラーをインストールし、実行しようとしていることがわかりました

if (assemblyfilename.EndsWith("someparticular.dll", StringComparison.InvariantCultureIgnoreCase))
{
    assemblyfilename = "someparticular_modified.dll";
}

今までに言う必要はありません。文字列比較で以前に使用されていないカルチャを使用すると、アセンブリの負荷が発生します。InvariantCultureも例外ではありません。


BeforeAssemblyLoadで確認できるので、「アセンブリの読み込み」は副作用だと思います。
Jacob Krall、

2
ワオ。これは、メンテナの足にぴったりのショットです。私は、BeforeAssemblyLoadハンドラーを作成することで、そのような多くの驚きにつながる可能性があると思います。
ウィッキー

20

これは、エラーメッセージ「保護されたメモリの読み取りまたは書き込みを試みました。多くの場合、他のメモリが破損していることを示しています」を生成する構造体を作成する方法の例です。成功と失敗の違いは非常に微妙です。

次の単体テストは、問題を示しています。

何がうまくいかなかったかを調べてみてください。

    [Test]
    public void Test()
    {
        var bar = new MyClass
        {
            Foo = 500
        };
        bar.Foo += 500;

        Assert.That(bar.Foo.Value.Amount, Is.EqualTo(1000));
    }

    private class MyClass
    {
        public MyStruct? Foo { get; set; }
    }

    private struct MyStruct
    {
        public decimal Amount { get; private set; }

        public MyStruct(decimal amount) : this()
        {
            Amount = amount;
        }

        public static MyStruct operator +(MyStruct x, MyStruct y)
        {
            return new MyStruct(x.Amount + y.Amount);
        }

        public static MyStruct operator +(MyStruct x, decimal y)
        {
            return new MyStruct(x.Amount + y);
        }

        public static implicit operator MyStruct(int value)
        {
            return new MyStruct(value);
        }

        public static implicit operator MyStruct(decimal value)
        {
            return new MyStruct(value);
        }
    }

頭が痛い…どうして動かないの?
jasonh 2009

2
ええと、私はこれを数ヶ月前に書いたが、なぜそれが起こったのか正確には思い出せない。
cbp 2009

10
コンパイラのバグのようです。+= 500コール:ldc.i4 500(のInt32として500を押す)、次いで、call valuetype Program/MyStruct Program/MyStruct::op_Addition(valuetype Program/MyStruct, valuetype [mscorlib]System.Decimal)-のように扱い、それはそうdecimal変換されず(96ビット)。あなた+= 500Mがそれを使うなら、それは正しくなります。それは、コンパイラー(おそらく暗黙のint演算子が原因で)ある方法で実行できると考えて、別の方法で実行することを決定したように見えます。
Marc Gravell

1
二重投稿して申し訳ありませんが、これはより限定的な説明です。私はこれを追加します、私はこれに少し気を配っていますが、なぜそれが起こるのか理解していますが 私にとって、これは構造体/値タイプの残念な制限です。 bytes.com/topic/net/answers/…–
Bennett Dill、

2
@Benでコンパイラエラーが発生するか、元の構造体に影響を与えない変更で問題ありません。アクセス違反はまったく別の獣です。安全な純粋なマネージコードを作成しているだけの場合、ランタイムはそれをスローするべきではありません。
CodesInChaos 2010年

18

C#は、配列が多次元でなく、型間に継承関係があり、型が参照型である限り、配列とリスト間の変換をサポートします

object[] oArray = new string[] { "one", "two", "three" };
string[] sArray = (string[])oArray;

// Also works for IList (and IEnumerable, ICollection)
IList<string> sList = (IList<string>)oArray;
IList<object> oList = new string[] { "one", "two", "three" };

これは機能しないことに注意してください:

object[] oArray2 = new int[] { 1, 2, 3 }; // Error: Cannot implicitly convert type 'int[]' to 'object[]'
int[] iArray = (int[])oArray2;            // Error: Cannot convert type 'object[]' to 'int[]'

11
string []は既にICloneable、IList、ICollection、IEnumerable、IList <string>、ICollection <string>、およびIEnumerable <string>を実装しているため、IList <T>の例は単なるキャストです。
ルーカス、

15

これは私が偶然に遭遇した中で最も奇妙なものです。

public class DummyObject
{
    public override string ToString()
    {
        return null;
    }
}

次のように使用します。

DummyObject obj = new DummyObject();
Console.WriteLine("The text: " + obj.GetType() + " is " + obj);

をスローしNullReferenceExceptionます。C#コンパイラによって複数の追加がコンパイルされ、が呼び出されることがわかりますString.Concat(object[])。.NET 4より前のバージョンでは、オブジェクトがnullかどうかがチェックされるが、ToString()の結果ではないConcatのオーバーロードにバグがあります。

object obj2 = args[i];
string text = (obj2 != null) ? obj2.ToString() : string.Empty;
// if obj2 is non-null, but obj2.ToString() returns null, then text==null
int length = text.Length;

これはECMA-334§14.7.4によるバグです。

2項の+演算子は、一方または両方のオペランドがtypeの場合に文字列連結を実行しますstring。文字列連結のオペランドがの場合、null空の文字列が置換されます。それ以外の場合、文字列以外のオペランドはToString、typeから継承された仮想メソッドを呼び出すことにより、文字列表現に変換されますobjectがをToString返すnull場合、空の文字列が置換されます。


3
うーん、しかし、私はこのフォールトが.ToString本当にnullを返すべきではないので、string.Empty を想像することができます。それにもかかわらず、フレームワークのエラー。
Dykam、2010年

12

興味深い-私が最初にそれを見たとき、それはC#コンパイラがチェックしているものだと思いましたが、干渉の可能性を取り除くためにILを直接放出したとしても、それはまだ起こりnewobjます。チェックしています。

var method = new DynamicMethod("Test", null, null);
var il = method.GetILGenerator();

il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Newarr, typeof(char));
il.Emit(OpCodes.Newobj, typeof(string).GetConstructor(new[] { typeof(char[]) }));

il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Newarr, typeof(char));
il.Emit(OpCodes.Newobj, typeof(string).GetConstructor(new[] { typeof(char[]) }));

il.Emit(OpCodes.Call, typeof(object).GetMethod("ReferenceEquals"));
il.Emit(OpCodes.Box, typeof(bool));
il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new[] { typeof(object) }));

il.Emit(OpCodes.Ret);

method.Invoke(null, null);

また、これtrueをチェックすることと同じですstring.Empty。つまり、このオペコードは空の文字列をインターンするために特別な動作をする必要があります。


スマートなアレックなんかじゃなくて、リフレクターのことを聞いたことがありますか?このような場合に非常に便利です。
RCIX 2009

3
あなたは賢くありません。あなたは要点を逃しています-私はこの1つのケースのために特定のILを生成したかったです。とにかく、Reflection.Emitがこのタイプのシナリオでは取るに足らないことを考えると、おそらくC#でプログラムを記述してからリフレクターを開き、バイナリを見つけ、メソッドを見つけるなどと同じくらい簡単です...そして、私はする必要さえありませんIDEに任せてください。
グレッグビーチ、

10
Public Class Item
   Public ID As Guid
   Public Text As String

   Public Sub New(ByVal id As Guid, ByVal name As String)
      Me.ID = id
      Me.Text = name
   End Sub
End Class

Public Sub Load(sender As Object, e As EventArgs) Handles Me.Load
   Dim box As New ComboBox
   Me.Controls.Add(box)          'Sorry I forgot this line the first time.'
   Dim h As IntPtr = box.Handle  'Im not sure you need this but you might.'
   Try
      box.Items.Add(New Item(Guid.Empty, Nothing))
   Catch ex As Exception
      MsgBox(ex.ToString())
   End Try
End Sub

出力は、「保護されたメモリを読み取ろうとしました。これは、他のメモリが破損していることを示しています。」


1
面白い!ただし、コンパイラのバグのように聞こえます。C#に移植しましたが、正常に動作します。とは言っても、Loadでスローされる例外には多くの問題があり、デバッガの有無に応じて動作は異なります。デバッガでキャッチすることはできますが、(ない場合は)できません。
Marc Gravell

すみません、忘れました。フォームにコンボボックスを追加する必要があります。
ジョシュア

これは、なんらかの恐ろしい内部通信メカニズムとしてSEHを使用したダイアログの初期化に関係していますか?Win32でそのようなことを漠然と覚えています。
Daniel Earwicker 2009年

1
これは上記のcbpと同じ問題です。返される値型は、それゆえ言っコピーに起因する任意のプロパティへの参照は、ビット・バケット地に向かっている、コピーです... bytes.com/topic/net/answers/...
ベネットディル

1
いいえ。ここには構造体はありません。実際にデバッグしました。ネイティブコンボボックスのリストアイテムコレクションにNULLを追加して、遅延クラッシュを引き起こします。
ジョシュア

10

PropertyInfo.SetValue()は、intをenumに、intをnull可能intに、enumをnull可能enumに割り当てることができますが、intをnull可能enumに割り当てることはできません。

enumProperty.SetValue(obj, 1, null); //works
nullableIntProperty.SetValue(obj, 1, null); //works
nullableEnumProperty.SetValue(obj, MyEnum.Foo, null); //works
nullableEnumProperty.SetValue(obj, 1, null); // throws an exception !!!

詳細な説明はこちら


10

型引数によってはあいまいになる可能性のあるメソッドを持つジェネリッククラスがある場合はどうでしょうか。最近、双方向辞書を書いているこの状況に出くわしました。Get()渡された引数の反対を返す対称メソッドを書きたかったのです。このようなもの:

class TwoWayRelationship<T1, T2>
{
    public T2 Get(T1 key) { /* ... */ }
    public T1 Get(T2 key) { /* ... */ }
}

あなたは、インスタンスを作るところならばすべてがうまくいいですT1し、T2さまざまな種類のとおりです。

var r1 = new TwoWayRelationship<int, string>();
r1.Get(1);
r1.Get("a");

しかし、T1T2が同じ場合(そしておそらく別のサブクラスである場合)は、コンパイラエラーです。

var r2 = new TwoWayRelationship<int, int>();
r2.Get(1);  // "The call is ambiguous..."

興味深いことに、2番目のケースの他のすべてのメソッドはまだ使用可能です。コンパイラエラーが発生するのは、あいまいになったメソッドの呼び出しのみです。興味深いケースですが、少し可能性が低くあいまいです。


メソッドのオーバーロードの反対者はこれを気に入るはずです^^。
クリスチャンクラウザー

1
よくわかりませんが、これは私にとっては理にかなっています。
スコットウィットロック

10

C#アクセシビリティパズル


次の派生クラスは、その基本クラスからプライベートフィールドにアクセスしており、コンパイラは静かに反対側を調べます。

public class Derived : Base
{
    public int BrokenAccess()
    {
        return base.m_basePrivateField;
    }
}

フィールドは確かにプライベートです:

private int m_basePrivateField = 0;

このようなコードをコンパイルするにはどうすればよいですか?

回答


コツはDerived、次の内部クラスとして宣言することですBase

public class Base
{
    private int m_basePrivateField = 0;

    public class Derived : Base
    {
        public int BrokenAccess()
        {
            return base.m_basePrivateField;
        }
    }
}

内部クラスには、外部クラスメンバーへのフルアクセスが与えられます。この場合、内部クラスも偶然外部クラスから派生しています。これにより、プライベートメンバーのカプセル化を「解除」できます。


これは実際に十分に文書化されています。msdn.microsoft.com/en-us/library/ms173120%28VS.80%29.aspx。特に外部クラスが静的な場合は、この機能が役立つ場合があります。

はい-もちろん文書化されています。しかし、このパズルを解く人はほとんどいなかったので、これはクールな雑学だと思いました。
Omer Mor 10/10/20

2
内部クラスに所有者を継承させることで、スタックオーバーフローが発生する可能性が非常に高いようです...
Jamie Treworgy

さらに別の同様の(と完全に正しい)場合は、オブジェクトが同じタイプの別のオブジェクトのプライベートメンバーにアクセスできることである:class A { private int _i; public void foo(A other) { int res = other._i; } }
オリビエJacot-Descombes

10

今日は素敵な小さなものを見つけました:

public class Base
{
   public virtual void Initialize(dynamic stuff) { 
   //...
   }
}
public class Derived:Base
{
   public override void Initialize(dynamic stuff) {
   base.Initialize(stuff);
   //...
   }
}

これはコンパイルエラーを投げます。

メソッド 'Initialize'の呼び出しは動的にディスパッチされる必要がありますが、ベースアクセス式の一部であるため、ディスパッチできません。動的な引数をキャストするか、ベースアクセスを削除することを検討してください。

base.Initialize(stuff as object);と書いた場合、それは完全に機能しますが、これはここでは「魔法の言葉」のようです。まったく同じように機能するため、すべてがまだ動的として受信されます...


8

私たちが使用しているAPIでは、ドメインオブジェクトを返すメソッドが特別な「nullオブジェクト」を返す場合があります。この実装では、比較演算子とEquals()メソッドがオーバーライドされtrueて、nullます。

したがって、このAPIのユーザーは次のようなコードを持っている可能性があります。

return test != null ? test : GetDefault();

または、おそらく次のようにもう少し冗長です:

if (test == null)
    return GetDefault();
return test;

どこGetDefault()我々が代わりに使用したいといういくつかのデフォルト値を返す方法がありますnull。ReSharperを使用していて、次のいずれかに書き換えることをお勧めしているので、驚きに襲われました。

return test ?? GetDefault();

検査対象ではなく、適切なのAPIから返されたヌル・オブジェクトである場合はnull、コードの挙動は、現在のNULL合体演算子として、実際のチェックが変更されたnull実行していない、operator=またはEquals()


1
実際にはC#のコーナーケースではありませんが、それを考えた親愛なる主よ!??
Ray Booysen、2010年

このコードは単にnull許容型を使用していませんか?したがって、ReSharperは「??」を推奨します。使用する。レイが言ったように、私はこれをコーナーケースとは思っていませんでした。または私は間違っていますか?
Tony

1
はい、型はnull可能です-さらにNullObjectがあります。それがコーナーケースであるかどうかはわかりませんが、少なくとも 'if(a!= null)がaを返すケースです。リターンb; ' 'return a ?? b '。私はそれがフレームワーク/ API設計の問題であることに完全に同意します-オブジェクトでtrueを返すための== nullのオーバーロードは確かに良い考えではありません!
Tor Livar 2010年

8

この奇妙なケースを考えてみましょう:

public interface MyInterface {
  void Method();
}
public class Base {
  public void Method() { }
}
public class Derived : Base, MyInterface { }

BaseおよびDerivedが同じアセンブリで宣言されている場合、インターフェイスを実装していなくBase::Methodても、コンパイラーは仮想化して(CILで)シールBaseします。

場合BaseDerived異なるアセンブリにあるコンパイルするときに、Derivedアセンブリの中で、それはメンバーを紹介しますので、コンパイラは、他のアセンブリを変更しないDerivedためにそれは明示的な実装になるMyInterface::Methodことはただの呼び出しを委任しますBase::Method

コンパイラは、インターフェイスに関するポリモーフィックディスパッチをサポートするためにこれを行う必要があります。つまり、そのメソッドを仮想化する必要があります。


それは確かに奇妙に聞こえます。後で調査する必要があります:)
Jon Skeet '05年

@Jon Skeet:C#でのロールの実装戦略を調査しているときに、これを見つけました。それについてあなたのフィードバックを得るのは素晴らしいことです!
ジョルダン

7

以下は、私が単に欠けていた一般的な知識かもしれませんが、ええ。少し前に、仮想プロパティを含むバグケースがありました。コンテキストを少し抽象化し、次のコードを検討して、指定した領域にブレークポイントを適用します。

class Program
{
    static void Main(string[] args)
    {
        Derived d = new Derived();
        d.Property = "AWESOME";
    }
}

class Base
{
    string _baseProp;
    public virtual string Property 
    { 
        get 
        {
            return "BASE_" + _baseProp;
        }
        set
        {
            _baseProp = value;
            //do work with the base property which might 
            //not be exposed to derived types
            //here
            Console.Out.WriteLine("_baseProp is BASE_" + value.ToString());
        }
    }
}

class Derived : Base
{
    string _prop;
    public override string Property 
    {
        get { return _prop; }
        set 
        { 
            _prop = value; 
            base.Property = value;
        } //<- put a breakpoint here then mouse over BaseProperty, 
          //   and then mouse over the base.Property call inside it.
    }

    public string BaseProperty { get { return base.Property; } private set { } }
}

しながら、Derivedオブジェクトコンテキスト、あなたは同じ動作を得ることができます追加するときbase.Property、時計、または入力としてbase.Property [クイックウォッチへ。

何が起こっているのかを理解するのに少し時間がかかりました。結局、私はクイックウォッチに啓蒙されました。クイックウォッチに移動してDerivedオブジェクトd(またはオブジェクトのコンテキストthis)を探索し、フィールドを選択するbaseと、クイックウォッチ上部の編集フィールドに次のキャストが表示されます。

((TestProject1.Base)(d))

つまり、baseがそのように置き換えられた場合、呼び出しは次のようになります。

public string BaseProperty { get { return ((TestProject1.Base)(d)).Property; } private set { } }

ウォッチ、クイックウォッチ、デバッグのマウスオーバーツールチップ"AWESOME""BASE_AWESOME"場合、ポリモーフィズムを考慮するときではなく、それを表示することは理にかなっています。なぜそれがキャストに変換されるのかはまだわかりcallません。1つの仮説は、これらのモジュールのコンテキストからは利用できない可能性があり、callvirt

とにかく、それは明らかに機能の点で何も変更せず、Derived.BasePropertyそれでも本当に戻ります"BASE_AWESOME"。したがって、これは実際のバグの原因ではなく、単に混乱を招くコンポーネントです。しかし、特にBaseプロジェクトで公開されておらず、サードパーティDLLとして参照されている場合に、開発者がデバッグセッション中にその事実を知らない開発者を誤解させる可能性があることは興味深いことに気付きました。

「おーい、待って..何?DLLのようなものです。何か面白いことをしています。」


それは特別なことではなく、それがオーバーライド作業の方法です。
コンフィギュレー

7

これはかなり難しいです。本当にBegin / EndInvokeをサポートするRealProxy実装を構築しようとしているときに遭遇しました(これを恐ろしいハックなしでは不可能にするMSに感謝します)。この例は基本的にCLRのバグです。BeginInvokeのアンマネージコードパスは、RealProxy.PrivateInvoke(および私のInvokeオーバーライド)からの戻りメッセージがIAsyncResultのインスタンスを返すことを検証しません。返されると、下部のテストで示されているように、CLRは信じられないほど混乱し、何が起こっているのかまったくわかりません。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Proxies;
using System.Reflection;
using System.Runtime.Remoting.Messaging;

namespace BrokenProxy
{
    class NotAnIAsyncResult
    {
        public string SomeProperty { get; set; }
    }

    class BrokenProxy : RealProxy
    {
        private void HackFlags()
        {
            var flagsField = typeof(RealProxy).GetField("_flags", BindingFlags.NonPublic | BindingFlags.Instance);
            int val = (int)flagsField.GetValue(this);
            val |= 1; // 1 = RemotingProxy, check out System.Runtime.Remoting.Proxies.RealProxyFlags
            flagsField.SetValue(this, val);
        }

        public BrokenProxy(Type t)
            : base(t)
        {
            HackFlags();
        }

        public override IMessage Invoke(IMessage msg)
        {
            var naiar = new NotAnIAsyncResult();
            naiar.SomeProperty = "o noes";
            return new ReturnMessage(naiar, null, 0, null, (IMethodCallMessage)msg);
        }
    }

    interface IRandomInterface
    {
        int DoSomething();
    }

    class Program
    {
        static void Main(string[] args)
        {
            BrokenProxy bp = new BrokenProxy(typeof(IRandomInterface));
            var instance = (IRandomInterface)bp.GetTransparentProxy();
            Func<int> doSomethingDelegate = instance.DoSomething;
            IAsyncResult notAnIAsyncResult = doSomethingDelegate.BeginInvoke(null, null);

            var interfaces = notAnIAsyncResult.GetType().GetInterfaces();
            Console.WriteLine(!interfaces.Any() ? "No interfaces on notAnIAsyncResult" : "Interfaces");
            Console.WriteLine(notAnIAsyncResult is IAsyncResult); // Should be false, is it?!
            Console.WriteLine(((NotAnIAsyncResult)notAnIAsyncResult).SomeProperty);
            Console.WriteLine(((IAsyncResult)notAnIAsyncResult).IsCompleted); // No way this works.
        }
    }
}

出力:

No interfaces on notAnIAsyncResult
True
o noes

Unhandled Exception: System.EntryPointNotFoundException: Entry point was not found.
   at System.IAsyncResult.get_IsCompleted()
   at BrokenProxy.Program.Main(String[] args) 

6

これがWindows Vista / 7の奇妙なことなのか、.Netの奇妙なことなのかはわかりませんが、しばらく頭を悩ませていました。

string filename = @"c:\program files\my folder\test.txt";
System.IO.File.WriteAllText(filename, "Hello world.");
bool exists = System.IO.File.Exists(filename); // returns true;
string text = System.IO.File.ReadAllText(filename); // Returns "Hello world."

Windows Vista / 7では、ファイルは実際に書き込まれます C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt


2
これは実際に、ビスタ(7ではない)のセキュリティ強化です。しかし、すばらしいのは、プログラムファイルパスを使用してファイルを読み取り、開くことができる一方で、エクスプローラーでそこを見ると何もないことです。これは最終的に私がそれを見つけるまでに、私が顧客@でほぼ1日の仕事を取りました。
Henri

それも間違いなくWindows 7のことです。それは私がそれに遭遇したときに使用していたものです。私はその背後にある理由を理解していますが、それを理解するのはまだイライラしていました。
Spencer Ruport 2010年

Vista / Win 7(技術的にはwinXP)では、アプリは技術的にユーザーデータとして、UsersフォルダーランドのAppDataフォルダーに書き込む必要があります。アプリケーションは、管理者特権を持っていない限り、programfiles / windows / system32 / etcに書き込むべきではありません。それらの特権は、プログラムのアップグレード/アンインストール/新しい機能のインストールとだけ言うべきです。だが!それでもsystem32 / windows / etcに書き込まないでください:)上記のコードを管理者として実行した場合(右クリック>管理者として実行)、理論的にはプログラムファイルのappフォルダーに書き込みます。
スティーブSyfuhs 2010年


6

C#コンパイラが無効なCILを生成する可能性があると思ったことはありませんか?これを実行すると、次のようになりますTypeLoadException

interface I<T> {
  T M(T p);
}
abstract class A<T> : I<T> {
  public abstract T M(T p);
}
abstract class B<T> : A<T>, I<int> {
  public override T M(T p) { return p; }
  public int M(int p) { return p * 2; }
}
class C : B<int> { }

class Program {
  static void Main(string[] args) {
    Console.WriteLine(new C().M(42));
  }
}

ただし、C#4.0コンパイラでどのように処理されるかはわかりません。

編集:これは私のシステムからの出力です:

C:\Temp>type Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1 {

  interface I<T> {
    T M(T p);
  }
  abstract class A<T> : I<T> {
    public abstract T M(T p);
  }
  abstract class B<T> : A<T>, I<int> {
    public override T M(T p) { return p; }
    public int M(int p) { return p * 2; }
  }
  class C : B<int> { }

  class Program {
    static void Main(string[] args) {
      Console.WriteLine(new C().M(11));
    }
  }

}
C:\Temp>csc Program.cs
Microsoft (R) Visual C# 2008 Compiler version 3.5.30729.1
for Microsoft (R) .NET Framework version 3.5
Copyright (C) Microsoft Corporation. All rights reserved.


C:\Temp>Program

Unhandled Exception: System.TypeLoadException: Could not load type 'ConsoleAppli
cation1.C' from assembly 'Program, Version=0.0.0.0, Culture=neutral, PublicKeyTo
ken=null'.
   at ConsoleApplication1.Program.Main(String[] args)

C:\Temp>peverify Program.exe

Microsoft (R) .NET Framework PE Verifier.  Version  3.5.30729.1
Copyright (c) Microsoft Corporation.  All rights reserved.

[token  0x02000005] Type load failed.
[IL]: Error: [C:\Temp\Program.exe : ConsoleApplication1.Program::Main][offset 0x
00000001] Unable to resolve token.
2 Error(s) Verifying Program.exe

C:\Temp>ver

Microsoft Windows XP [Version 5.1.2600]

C#3.5コンパイラとC#4コンパイラの両方で動作します...
Jon Skeet

私のシステムでは動作しません。質問に出力を貼り付けます。
ジョルダン

.NET 3.5では失敗しました(4.0をテストする時間はありません)。そして、VB.NETコードで問題を再現できます。
Mark Hurd

3

C#には、クロージャの処理方法に関して、非常にエキサイティングなものがあります。

スタック変数の値をクロージャのない変数にコピーする代わりに、プリプロセッサマジックは、変数のすべての発生をオブジェクトにラップして、スタックから直接-ヒープに移動します。:)

おそらく、C#はML自体(AFAIKをコピーするスタック値を使用する)よりも機能的に完全な(またはラムダ完全なハフ)言語です。C#と同様に、F#にもその機能があります。

それは私に多くの喜びをもたらします、MSの皆さん、ありがとう!

奇妙なことではありませんが、スタックベースのVM言語からは本当に予期しないことが起こります:)


3

少し前に私が尋ねた質問から:

条件演算子は暗黙的にキャストできませんか?

与えられた:

Bool aBoolValue;

どこにaBoolValueTrueまたはFalseが割り当てられます。

以下はコンパイルされません:

Byte aByteValue = aBoolValue ? 1 : 0;

しかし、これは:

Int anIntValue = aBoolValue ? 1 : 0;

提供された答えもかなり良いです。


私はもののve not test it IバイトaByteValue = aBoolValue:これが動作することを確信していますか?(バイト)1 :(バイト)0; または:Byte aByteValue =(Byte)(aBoolValue?1:0);
Alex Pacurar

2
はい、アレックス、それはうまくいくでしょう。キーは暗黙的キャストにあります。1 : 0単独ではByteではなくintに暗黙的にキャストされます。
MPelletier 2010年

2

C#でのスコープ指定は、本当に奇妙な場合があります。一例を挙げましょう。

if (true)
{
   OleDbCommand command = SQLServer.CreateCommand();
}

OleDbCommand command = SQLServer.CreateCommand();

コマンドが再宣言されているため、これはコンパイルに失敗しますか?stackoverflowのこのスレッド私のブログなぜこのように機能するかについて、いくつかの興味深い推測があります。


34
私はそれを特に奇妙だとは思わない。ブログで「完全に正しいコード」と呼んでいるものは、言語仕様によると完全に正しくありません。それはあなたがいただきたい、いくつかの架空の言語で正しいかもしれようであることをC#が、言語仕様は、C#で、それは無効だということは明白です。
Jon Skeet、

7
まあそれはC / C ++で有効です。そして、それはC#なので、まだ機能するようにしたかったのです。私が最もバグを感じるのは、コンパイラーがこれを行う理由がないということです。ネストされたスコーピングを行うことは難しくありません。私はそれがすべて最小の驚きの要素に帰着すると思います。それは仕様がこれとそれを言っているということかもしれませんが、それがそのように振る舞うことが完全に非論理的であるならば、それは私にはあまり役に立ちません。
アンダースルーンジェンセン

6
C#!= C / C ++。cout << "Hello World!"も使用しますか?<< endl; 代わりにConsole.WriteLine( "Hello World!");? また、非論理的ではありません。仕様を読んでください。
クレデンス2009年

9
言語の中核の一部であるスコーピングルールについて話しています。あなたは標準ライブラリについて話しています。しかし、プログラミングを始める前に、c#言語の小さな仕様を読むだけでよいことは明らかです。
Anders Rune Jensen、

6
Eric Lippertが実際に最近C#がそのように設計された理由を投稿しました:blogs.msdn.com/ericlippert/archive/2009/11/02/…。要約は、変更が意図しない結果をもたらす可能性が低いためです。
エレファント2009年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.