c#のrefおよびoutパラメーターとバリアントとしてマークすることはできません
-
03-10-2019 - |
質問
声明はどういう意味ですか?
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)を試してみましたが、うまくいきました。
解決
「Out」とは、大まかに言えば、出力位置にのみ表示されます」。
「In」は、大まかに言えば、入力位置にのみ表示されます」。
実際のストーリーはそれよりも少し複雑ですが、ほとんどの場合、これが当てはまるため、キーワードが選択されました。
インターフェイスの方法または代表者によって表される方法を検討してください。
delegate void Foo</*???*/ T>(ref T item);
Tは入力位置に表示されますか?はい。発信者は、アイテムを介してtの値を渡すことができます。 Callee Fooはそれを読むことができます。したがって、tに「out」とマークすることはできません。
Tは出力位置に表示されますか?はい。 Calleeはアイテムに新しい値を書くことができ、それを発信者が読むことができます。したがって、tに「in」とマークすることはできません。
したがって、tが「ref」正式なパラメーターに表示される場合、tは内外のいずれかとしてマークされません。
物事がどのようにうまくいかないかのいくつかの本当の例を見てみましょう。これが合法だったとします:
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);
まあ私の猫の犬、私たちはちょうど猫の樹皮を作りました。 「アウト」は合法ではありません。
「in」はどうですか?
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」の唯一の違いは、 コンパイラ Calleeによって割り当てられる前にOUTパラメーターから読み取りを禁止し、Calleeが正常に戻る前にコンパイラが割り当てを必要とすることを禁止します。このインターフェイスの実装を書いた人 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
と out
2つのことを意味します。これはいくつかの混乱を引き起こすでしょう。それをクリアさせてください:
in
,out
とref
aで パラメーターリストの正式なパラメーター宣言 「このパラメーターは、発信者が提供する変数のエイリアス」を意味します。ref
「Calleeはエイリアス変数を読み書きするか書き留めることができ、呼び出しの前に割り当てられることが知られている必要があります。out
「カリーは、エイリアスが正常に戻る前にエイリアスを介してエイリアス変数を記述する必要がある」を意味します。また、変数が間違いなく割り当てられない可能性があるため、Calleeがエイリアスを介してエイリアスを介してエイリアスを介して読み取ってはなりません。in
「Calleeはエイリアス変数を読み取ることができますが、エイリアスを介してそれを書き込まないかもしれません」を意味します。の目的in
まれなパフォーマンスの問題を解決することです。それにより、大きな構造体は「価値によって」渡されなければなりませんが、そうすることは費用がかかります。実装の詳細として、in
パラメーターは通常、ポインターサイズの値を介して渡されます。これは、値でコピーするよりも速いですが、逆方向に遅くなります。- CLRの観点から、
in
,out
とref
すべて同じことです。誰がどの時間にどのような変数、CLRが知らないか気にしないかについて誰が読み、書き込むかについてのルール。 - 分散に関するルールを実施するのはCLRであるため、に適用されるルール
ref
また適用しますin
とout
パラメーター。
対照的に、 in
と out
タイプパラメーター宣言では、「このタイプパラメーターは共変量で使用してはならない」と「このタイプパラメーターは、矛盾した方法で使用してはならない」ということを意味します。
上記のように、私たちは選択しました in
と out
私たちが見るなら、それらの修飾子のために IFoo<in T, out U>
それから T
「入力」位置で使用されます U
「出力」位置で使用されます。そうではありませんが 厳密に 確かに、99.9%のユースケースでは、それが役立つニーモニックであることが十分に当てはまります。
残念なことです interface IFoo<in T, out U> { void Foo(in T t, out U u); }
うまくいくはずのように見えるので違法です。 CLR Verifierの視点から、それらは両方とも ref
パラメーター、したがって読み取りワイト。
これは、論理的に協力すべき2つの機能が実装の詳細な理由でうまく機能しない奇妙で意図しない状況の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);
OUTパラメーターと共分散を使用することは可能ですが、2つの構造が必要です。たとえば、拡張メソッドにoutパラメーターを配置できます。
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; }
}
ただし、次のコードをコンパイルできます。
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!
}