なぜこれ(null ||!TryParse)は条件付きで「割り当てられていないローカル変数の使用」を引き起こすのですか?


98

次のコードでは、割り当てられていないローカル変数 "numberOfGroups"使用さます

int numberOfGroups;
if(options.NumberOfGroups == null || !int.TryParse(options.NumberOfGroups, out numberOfGroups))
{
    numberOfGroups = 10;
}

ただし、このコードは正常に機能します(ただし、ReSharperはこれ= 10は冗長であると言っています)。

int numberOfGroups = 10;
if(options.NumberOfGroups == null || !int.TryParse(options.NumberOfGroups, out numberOfGroups))
{
    numberOfGroups = 10;
}

私は何かが足りないのですか、それともコンパイラが気に入らないの||ですか?

これをdynamic問題の原因に絞り込みました(options上のコードの動的変数でした)。問題はまだ残っていますが、なぜこれができないのですか?

このコードコンパイルされません

internal class Program
{
    #region Static Methods

    private static void Main(string[] args)
    {
        dynamic myString = args[0];

        int myInt;
        if(myString == null || !int.TryParse(myString, out myInt))
        {
            myInt = 10;
        }

        Console.WriteLine(myInt);
    }

    #endregion
}

ただし、このコード次のことを行います。

internal class Program
{
    #region Static Methods

    private static void Main(string[] args)
    {
        var myString = args[0]; // var would be string

        int myInt;
        if(myString == null || !int.TryParse(myString, out myInt))
        {
            myInt = 10;
        }

        Console.WriteLine(myInt);
    }

    #endregion
}

これがdynamic要因だとは思いませんでした。


outparamに渡された値を入力として使用していないことを知っているほど賢いとは思わない
Charleh

3
ここに示すコードは、記述された動作を示していません。それはうまくいきます。私たちが自分でコンパイルできるというあなたが説明しいる動作実際に示すコードを投稿してください。ファイル全体を渡してください。
Eric Lippert 2013

8
ああ、今私たちは面白いものを持っています!
Eric Lippert 2013

1
コンパイラがこれによって混乱するのは、それほど驚くことではありません。動的呼び出しサイトのヘルパーコードには、outパラメーターへの割り当てを保証しない制御フローが含まれている可能性があります。問題を回避するためにコンパイラーがどのヘルパーコードを生成する必要があるか、またはそれが可能かどうかを検討することは確かに興味深いことです。
CodesInChaos 2013

1
一見、これはバグのように見えます。
Eric Lippert 2013

回答:


73

これはコンパイラのバグだと思います。いい発見!

編集:クォーターマイスターが示すように、これはバグではありません。dynamicは、初期化されないtrue可能性がある奇妙な演算子を実装するy場合があります。

これが最小限の再現です:

class Program
{
    static bool M(out int x) 
    { 
        x = 123; 
        return true; 
    }
    static int N(dynamic d)
    {
        int y;
        if(d || M(out y))
            y = 10;
        return y; 
    }
}

それが違法であるべき理由は私にはわかりません。dynamicをboolに置き換えると、問題なくコンパイルされます。

明日はC#チームと実際に会います。私は彼らにそれを言及します。エラーの謝罪!


6
頭がおかしくならないことを嬉しく思います:)それ以来、コードを更新して、TryParseのみに依存するようにしたので、今は設定します。あなたの洞察をありがとう!
Brandon Martinez

4
@NominSim:ランタイム分析が失敗したとします。ローカルが読み取られる前に例外がスローされます。ランタイム分析が成功したとします。実行時に、dがtrueでyが設定されるか、dがfalseでMがyを設定します。どちらの方法でも、yが設定されます。分析はランタイムが何も変更しないまで延期されるという事実。
Eric Lippert 2013

2
誰かが気になった場合のために:私がチェックしたところ、Monoコンパイラーはそれを正しく理解しています。imgur.com/g47oquT
Dan Tao

17
の値は演算子がdオーバーロードされた型である可能性があるため、コンパイラの動作は実際には正しいと思いますtrue。どちらの分岐も行われない例を使用して回答を投稿しました。
Quartermeister 2013

2
@Quartermeisterの場合、Monoコンパイラーはそれを
誤解し

52

動的式の値がオーバーロードされたtrue演算子を持つ型である場合、変数の割り当てが解除される可能性があります。

||オペレータが起動しますtrue右辺を評価するかどうかを決定するためにオペレータをして、ifステートメントが呼び出されますtrue、その本体を評価するかどうかを決定するための演算子を。通常の場合bool、これらは常に同じ結果を返すため、正確に1つが評価されますが、ユーザー定義演算子の場合、そのような保証はありません。

Eric Lippertの再現を基に、次の短い完全なプログラムは、どちらのパスも実行されず、変数に初期値がある場合を示しています。

using System;

class Program
{
    static bool M(out int x)
    {
        x = 123;
        return true;
    }

    static int N(dynamic d)
    {
        int y = 3;
        if (d || M(out y))
            y = 10;
        return y;
    }

    static void Main(string[] args)
    {
        var result = N(new EvilBool());
        // Prints 3!
        Console.WriteLine(result);
    }
}

class EvilBool
{
    private bool value;

    public static bool operator true(EvilBool b)
    {
        // Return true the first time this is called
        // and false the second time
        b.value = !b.value;
        return b.value;
    }

    public static bool operator false(EvilBool b)
    {
        throw new NotImplementedException();
    }
}

8
いい仕事です。これをC#のテストチームと設計チームに渡しました。明日彼らに会ったときに、彼らにコメントがあるかどうかを確認します。
Eric Lippert 2013

3
これは私には非常に奇妙です。なぜd2回評価する必要があるのですか?(あなたが示したように、それが明らかにそうあることに異議はありません。)true(最初の演算子の呼び出しから、によって引き起こされた||)の評価結果がifステートメントに「渡される」と期待していました。たとえば、関数呼び出しをそこに配置すると、それは確かに起こります。
Dan Tao

3
@DanTao:d予想どおり、式は1回だけ評価されます。それはだtrueことにより、1回、2回呼び出されていますオペレータ||と一度if
Quartermeister 2013

2
@DanTao:として別のステートメントに配置すると、より明確になる可能性がありますvar cond = d || M(out y); if (cond) { ... }。まずdEvilBoolオブジェクト参照を取得するために評価します。を評価する||には、まずEvilBool.trueその参照で呼び出します。これはtrueを返すので、短絡してを呼び出さずMに、への参照を割り当てcondます。次に、ifステートメントに移ります。if声明は、呼び出すことによって、その条件を評価しますEvilBool.true
Quartermeister 2013

2
これは本当にクールです。私は真または偽の演算子があることを知りませんでした。
IllidanS4はモニカに2013

7

MSDN(強調鉱山)から:

動的型は、それが発生する操作でコンパイル時の型チェックバイパスできるようにします。代わりに、これらの操作は実行時に解決されます。動的型は、OfficeオートメーションAPIなどのCOM API、IronPythonライブラリなどの動的API、およびHTMLドキュメントオブジェクトモデル(DOM)へのアクセスを簡素化します。

動的型は、ほとんどの場合、型オブジェクトのように動作します。ただし、動的型の式を含む操作は、コンパイラーによって解決または型チェックされません。

コンパイラーは、型検査または動的型の式を含む操作を解決しないため、変数がを使用して割り当てられることを保証できませんTryParse()


最初の条件が満たされている場合numberGroupsは(if trueブロック内で)割り当てられ、満たされていない場合は2番目の条件が割り当てを保証します(を介してout)。
leppie 2013

1
それは興味深い考えです、コードはmyString == null(のみに依存するTryParse)なしで正常にコンパイルされます。
Brandon Martinez

1
@leppie重要な点は、最初の条件(実際にはif式全体)にはdynamic変数が含まれるため、コンパイル時に解決されない(したがって、コンパイラーがそれらの仮定を行うことができない)ことです。
NominSim 2013

@NominSim:私はあなたのポイントを理解します:) +1コンパイラからの犠牲になる可能性があります(C#ルールに違反します)が、他の提案はバグを示唆しているようです。エリックの抜粋は、これは犠牲ではなくバグであることを示しています。
leppie 2013

@NominSimこれは正しくありません。特定のコンパイラー関数が据え置かれているからといって、すべてがそうであるとは限りません。わずかに異なる状況下で、動的な式が存在するにもかかわらず、コンパイラーが問題なく明確な代入分析を行うことを示す証拠はたくさんあります。
dlev、2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.