14

ステートメントはどういう意味ですか?

ここから

C#のrefおよびoutパラメータであり、バリアントとしてマークすることはできません。

1)次のことができないということですか。

public class SomeClass<R, A>: IVariant<R, A>
{
    public virtual R DoSomething( ref A args )
    {
        return null;
    }
}

2)それとも、私が次のものを持てないという意味ですか。

public delegate R Reader<out R, in A>(A arg, string s);

public static void AssignReadFromPeonMethodToDelegate(ref Reader<object, Peon> pReader)
{
    pReader = ReadFromPeon;
}

static object ReadFromPeon(Peon p, string propertyName)
    {
        return p.GetType().GetField(propertyName).GetValue(p);
    }

static Reader<object, Peon> pReader;

static void Main(string[] args)
    {
        AssignReadFromPeonMethodToDelegate(ref pReader);
        bCanReadWrite = (bool)pReader(peon, "CanReadWrite");

        Console.WriteLine("Press any key to quit...");
        Console.ReadKey();
    }

(2)を試してみましたが、うまくいきました。

4

4 に答える 4

53

「アウト」とは、大まかに言うと「出力位置にのみ表示される」という意味です。

「in」は、大まかに言えば、「入力位置にのみ表示される」ことを意味します。

実際の話はそれよりも少し複雑ですが、ほとんどの場合これが当てはまるため、キーワードが選択されました。

インターフェイスのメソッドまたはデリゲートによって表されるメソッドについて考えてみます。

delegate void Foo</*???*/ T>(ref T item);

Tは入力位置に表示されますか?はい。呼び出し元は、アイテムを介してTの値を渡すことができます。呼び出し先のFooはそれを読むことができます。したがって、Tを「アウト」とマークすることはできません。

Tは出力位置に表示されますか?はい。呼び出し先はアイテムに新しい値を書き込むことができ、呼び出し元はそれを読み取ることができます。したがって、Tを「in」とマークすることはできません。

したがって、Tが「ref」仮パラメータに含まれている場合、Tをinまたはoutとしてマークすることはできません。

物事がどのようにうまくいかないかのいくつかの実際の例を見てみましょう。これが合法であると仮定します。

delegate void X<out T>(ref T item);
...
X<Dog> x1 = (ref Dog d)=>{ d.Bark(); }
X<Animal> x2 = x1; // covariant;
Animal a = new Cat();
x2(ref a);

私の猫を犬にしましょう。猫の鳴き声を作りました。「アウト」は合法ではありません。

「で」はどうですか?

delegate void X<in T>(ref T item);
...
X<Animal> x1 = (ref Animal a)=>{ a = new Cat(); }
X<Dog> x2 = x1; // contravariant;
Dog d = new Dog();
x2(ref d);

そして、犬しか入れられない変数に猫を入れるだけです。Tも「in」とマークすることはできません。

outパラメータはどうですか?

delegate void Foo</*???*/T>(out T item);

?これで、Tは出力位置にのみ表示されます。Tを「アウト」としてマークすることは合法である必要がありますか?

残念だけど違う。「out」は、実際には舞台裏の「ref」と同じです。「out」と「ref」の唯一の違いは、コンパイラーが、呼び出し先によって割り当てられる前にoutパラメーターからの読み取りを禁止していることと、呼び出し先が正常に戻る前にコンパイラーが割り当てを要求することです。このインターフェイスの実装をC#以外の.NET言語で記述した人は、初期化される前にアイテムから読み取ることができるため、入力として使用できます。したがって、この場合、Tを「アウト」としてマークすることは禁止されています。それは残念ですが、私たちにできることは何もありません。CLRの型安全性規則に従わなければなりません。

さらに、「out」パラメータの規則は、に書き込まれる前に入力に使用できないことです。書き込み後の入力に使用できないというルールはありません。許可したとしましょう

delegate void X<out T>(out T item);
class C
{
    Animal a;
    void M()
    {
        X<Dog> x1 = (out Dog d) => 
        { 
             d = null; 
             N(); 
             if (d != null) 
               d.Bark(); 
        };
        x<Animal> x2 = x1; // Suppose this were legal covariance.
        x2(out this.a);
    }
    void N() 
    { 
        if (this.a == null) 
            this.a = new Cat(); 
    }
}

もう一度猫の鳴き声を作りました。Tを「アウト」にすることはできません。

この方法で入力用のパラメーターを使用することは非常に愚かですが、合法です。


更新:C#7がin正式なパラメーター宣言として追加されました。これは、両方がinあり、out2つの意味があることを意味します。これにより、混乱が生じます。それを明確にしましょう:

  • inoutおよびパラメータリストref正式なパラメータ宣言では、「このパラメータは呼び出し元によって提供された変数のエイリアスです」を意味します。
  • ref「呼び出し先はエイリアス変数の読み取りまたは書き込みが可能であり、呼び出し前に割り当てられていることがわかっている必要があります。
  • out「呼び出し先は、正常に戻る前に、エイリアスを介してエイリアス変数を書き込む必要があります」を意味します。また、変数が明確に割り当てられていない可能性があるため、呼び出し先はエイリアスを書き込む前にエイリアスを介してエイリアス変数を読み取ってはならないことも意味します。
  • in「呼び出し先はエイリアス変数を読み取ることはできますが、エイリアスを介して書き込むことはできません」を意味します。の目的はin、まれなパフォーマンスの問題を解決することです。この問題では、大きな構造体を「値で」渡す必要がありますが、そうするにはコストがかかります。実装の詳細として、inパラメーターは通常、ポインターサイズの値を介して渡されます。これは、値によるコピーよりも高速ですが、間接参照では低速です。
  • CLRの観点からは、、、inおよびoutrefすべて同じものです。誰がいつどの変数を読み書きするかについてのルールでは、CLRは知りません。
  • 分散に関するルールを適用するのはCLRであるため、適用されるルールはパラメータにrefも適用されinますout

対照的に、inおよびouton型パラメーター宣言は、それぞれ「この型パラメーターを共変的に使用してはならない」および「この型パラメーターを反変的に使用してはならない」ことを意味します。

上記のように、これらの修飾子には、を選択inしました。これは、「入力」位置で使用され、「出力」位置で使用されるためです。これは厳密には当てはまりませんが、99.9%のユースケースでは十分に当てはまり、有用なニーモニックです。outIFoo<in T, out U>TU

interface IFoo<in T, out U> { void Foo(in T t, out U u); }それが機能するはずであるように見えるので、それは違法であるのは残念です。CLR検証者の観点からは、これらは両方ともrefパラメーターであり、したがって読み取り/書き込みであるため、機能しません。

これは、論理的に連携するはずの2つの機能が、実装の詳細な理由で連携してうまく機能しないという、奇妙で意図しない状況の1つにすぎません。

于 2010-05-20T20:24:05.457 に答える
1

これは、次の宣言を行うことができないことを意味します。

public delegate R MyDelegate<out R, in A>(ref A arg);

編集: @Eric Lippertは、これはまだ合法であると私に訂正しました:

public delegate void MyDelegate<R, in A>(A arg, out R s);

R汎用パラメーターはバリアントとしてマークされていないため、実際には意味があります。したがって、ルールに違反しません。ただし、これはまだ違法です。

public delegate void MyDelegate<out R, in A>(A arg, out R s);
于 2010-05-20T17:57:06.240 に答える
1

Eric Lippertは、この制約が存在する理由について優れた説明をしています。

この制限を回避しようとしている場合。あなたはこのようにそれを行うことができます:

public static class ResultExtension{
    public static bool TryGetValue<T>(this IResult<T> self, out T res) {
        if (self.HasValue) {
            res = self.Value;
            return true;

        }
        res = default;
        return false;
    }
}

public interface IResult<out T>
{
    bool HasValue { get; }
    T Value { get; }
}

これは、2つの構造があるために機能します。1つの構造は共分散を取得し、もう1つの構造はoutパラメーターを取得します。outパラメーターはバリアントとしてマークされていないため、コンパイラーは問題ありません。

于 2018-09-30T01:00:16.127 に答える
0

ただし、次のコードをコンパイルできます。

interface IFoo<out T>
{
    T Get();

    //bool TryGet(out T value); // doesn't work: Invalid variance: The type parameter 'T' must be invariantly valid on 'IFoo<T>.TryGet(out T)'. 'T' is covariant.

    bool TryGet(Action<T> value); // works!
}
于 2011-12-22T13:50:46.297 に答える