12

型制約のあるジェネリックを使用する C# コードをコンパイルしているときに、興味深い好奇心に出くわしました。説明のために簡単なテストケースを書きました。Visual Studio 2010 で .NET 4.0 を使用しています。

namespace TestCast
{
    public class Fruit { }

    public class Apple : Fruit { }

    public static class Test
    {
        public static void TestFruit<FruitType>(FruitType fruit) 
            where FruitType : Fruit
        {
            if (fruit is Apple)
            {
                Apple apple = (Apple)fruit;
            }
        }
    }
}

Apple へのキャストは次のエラーで失敗します:「タイプ 'FruitType' を 'TestCast.Apple' に変換できません」。ただし、as演​​算子を使用するように行を変更すると、エラーなしでコンパイルされます。

Apple apple = fruit as Apple;

誰かがなぜこれが当てはまるのか説明してもらえますか?

4

6 に答える 6

23

この質問は、 2015 年 10 月のブログ記事の基礎として使用しました。素晴らしい質問をありがとう!

誰かがなぜこれが当てはまるのか説明してもらえますか?

「なぜ」という質問には答えにくい。答えは「仕様がそう言っているから」であり、自然な質問は「なぜ仕様がそう言っているのか?」です。

それでは、質問をより明確にしましょう。

与えられたキャスト演算子を制約された型パラメーターで無効にするという決定に影響を与えた言語設計要因は何ですか?

次のシナリオを検討してください。基本型 Fruit、派生型 Apple および Banana があり、ここで重要な部分である、Apple から Banana へのユーザー定義の変換が行われます。

として呼び出された場合、これは何をすべきだと思いますM<Apple>か?

void M<T>(T t) where T : Fruit
{
    Banana b = (Banana)t;
}

コードを読んだほとんどの人は、これは Apple から Banana へのユーザー定義の変換を呼び出すべきだと言うでしょう。ただし、C# ジェネリックは C++ テンプレートではありません。メソッドは、すべてのジェネリック構造に対してゼロから再コンパイルされるわけではありません。むしろ、メソッドは1 回コンパイルされ、そのコンパイル中に、キャストを含むすべての演算子の意味が、可能なすべての一般的なインスタンス化について決定されます。

の本体にM<Apple>は、ユーザー定義の変換が必要です。の本体にM<Banana>は ID 変換があります。 M<Cherry>エラーになります。ジェネリックメソッドで演算子の 3 つの異なる意味を持つことはできないため、演算子は拒否されます。

代わりに、次のことを行う必要があります。

void M<T>(T t) where T : Fruit
{
    Banana b = (Banana)(object)t;
}

これで、両方の変換が明確になりました。object への変換は、暗黙的な参照変換です。Banana への変換は、明示的な参照変換です。ユーザー定義の変換が呼び出されることはありません。これが Cherry で構築されている場合、オブジェクトからキャストするときは常にそうであるように、コンパイル時ではなく実行時にエラーが発生します。

as演算子はキャスト演算子とは異なります。asオペレーターがユーザー定義の変換を呼び出すことはないため、指定された型に関係なく、常に同じことを意味します。したがって、キャストが違法となるコンテキストで使用できます。

于 2013-10-03T18:37:41.727 に答える
4

「as 演算子はキャスト操作に似ています。ただし、変換が不可能な場合、as は例外を発生させる代わりに null を返します。」

asコンパイラは、演算子の使用時に未定義の明示的なキャストをチェックしないため、演算子でコンパイル時エラーが発生することはありませんas。その目的は、有効かどうかに関係なく実行時のキャストを試行できるようにすることです。そうでない場合は、例外をスローするのではなく、null を返します。

fruitいずれにせよ、 is notの場合を処理する予定がある場合はApple、チェックを次のように実装する必要があります。

var asApple = fruit as Appple;
if(asApple == null)
{
    //oh no
}
else
{
   //yippie!
}
于 2013-10-03T18:16:38.873 に答える
2

なぜコンパイラがあなたのコードを思い通りに書けないのかという質問に答えるために。if は実行時に評価されるため、コンパイラはキャストが有効な場合にのみ発生することを知りません。

それを機能させるには、ifで次のようなことを「できます」:

Apple apple = (Apple)(object)fruit;

ここで、同じ質問についてさらにいくつか説明します。

もちろん、as演算子を使用するのが最善の解決策です。

于 2013-10-03T18:27:23.610 に答える
0

msdn doc で説明されています

as 演算子はキャスト操作に似ています。ただし、変換が不可能な場合、as は例外を発生させる代わりに null を返します。次の例を検討してください。

expression as type このコードは、expression 変数が 1 回だけ評価されることを除いて、次の式と同等です。

式は型?(type)expression : (type)null as 演算子は、参照変換、null 許容変換、およびボックス化変換のみを実行することに注意してください。as 演算子は、代わりにキャスト式を使用して実行する必要がある、ユーザー定義の変換などの他の変換を実行できません。

于 2013-10-03T18:17:02.807 に答える