C# 中的 ref 和 out 参数不能标记为变体

发布于 2024-09-02 00:09:49 字数 1066 浏览 5 评论 0原文

该声明的含义是什么?

从此处

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)并且成功了。

What does the statement mean?

From here

ref and out parameters in C# and
cannot be marked as variant.

1) Does it mean that the following can not be done.

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

2) Or does it mean I cannot have the following.

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();
    }

I tried (2) and it worked.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

潇烟暮雨 2024-09-09 00:09:49

粗略地说,“out”的意思是“仅出现在输出位置”。

粗略地说,“in”的意思是“仅出现在输入位置”。

真实的故事比这要复杂一些,但选择关键词是因为大多数情况都是这样。

考虑接口的方法或委托表示的方法:

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

T 出现在输入位置吗?是的。调用者可以在via item中传递T的值;被调用者 Foo 可以读取它。因此 T 不能被标记为“out”。

T出现在输出位置吗?是的。被调用者可以向 item 写入一个新值,然后调用者可以读取该值。因此 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”。

那么输出参数呢?

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

?现在T只出现在输出位置。将 T 标记为“out”是否合法?

不幸的是没有。 “out”实际上与幕后的“ref”没有什么不同。 “out”和“ref”之间的唯一区别是,编译器禁止在被调用者赋值之前读取 out 参数,并且编译器要求在被调用者正常返回之前进行赋值。使用 C# 以外的 .NET 语言编写此接口的实现的人将能够在初始化之前读取该项目,因此可以将其用作输入。因此,在这种情况下,我们禁止将 T 标记为“out”。这是令人遗憾的,但我们对此无能为力;我们必须遵守 CLR 的类型安全规则。

此外,“输出”参数的规则是它们在写入之前不能用于输入。。没有规定它们在写入后不能用于输入。它们被写入。假设我们

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“出局”。

以这种方式使用out参数进行输入是非常愚蠢的,但却是合法的。


更新:C# 7 添加了 in 作为形式参数声明,这意味着我们现在同时拥有 inout 意味着两件事;这会造成一些混乱。让我澄清一下:

  • 参数列表中的形式参数声明中的 inoutref 表示“该参数是调用者提供的变量的别名”。
  • ref 表示“被调用者可以读取或写入别名变量,并且必须在调用之前知道它已被赋值。
  • out 表示“被调用者必须通过以下方式写入别名变量:正常返回之前的别名”。这也意味着被调用者在写入别名之前不能通过别名读取别名变量,因为该变量可能没有被明确赋值。
  • in表示“被调用者可以读取别名变量,但不通过别名写入它”。 in 的目的是解决一个罕见的性能问题,即必须“按值”传递大型结构,但这样做的成本很高作为实现细节,in 参数通常通过指针大小的值传递,这比按值复制更快,但从
  • CLR 的角度来看,in 更慢。outref 都是同一件事;关于谁在什么时间读取和写入什么变量的规则,CLR 不知道也不关心。
  • 由于 CLR 强制执行有关方差的规则,因此适用于 ref 的规则也适用于 inout 参数。

相反,类型参数声明上的 inout 表示“此类型参数不得以协变方式使用”和“此类型参数不得以逆变方式使用”方式”分别。

如上所述,我们为这些修饰符选择了 inout,因为如果我们看到 IFooT 用于“输入”位置,U 用于“输出”位置。尽管严格来说这不是真的,但在 99.9% 的用例中,它是一个有用的助记符,这足够真实。

不幸的是接口IFoo{ void Foo(in T t, out U u); } 是非法的,因为它看起来应该有效。它无法工作,因为从 CLR 验证程序的角度来看,这些都是 ref 参数,因此是读写的。

这只是那些奇怪的、意想不到的情况之一,逻辑上应该协同工作的两个功能由于实现细节原因而不能很好地协同工作。

"out" means, roughly speaking, "only appears in output positions".

"in" means, roughly speaking, "only appears in input positions".

The real story is a bit more complicated than that, but the keywords were chosen because most of the time this is the case.

Consider a method of an interface or the method represented by a delegate:

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

Does T appear in an input position? Yes. The caller can pass a value of T in via item; the callee Foo can read that. Therefore T cannot be marked "out".

Does T appear in an output position? Yes. The callee can write a new value to item, which the caller can then read. Therefore T cannot be marked "in".

Therefore if T appears in a "ref" formal parameter, T cannot be marked as either in or out.

Let's look at some real examples of how things go wrong. Suppose this were legal:

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);

Well dog my cats, we just made a cat bark. "out" cannot be legal.

What about "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);

And we just put a cat in a variable that can only hold dogs. T cannot be marked "in" either.

What about an out parameter?

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

? Now T only appears in an output position. Should it be legal to make T marked as "out"?

Unfortunately no. "out" actually is not different than "ref" behind the scenes. The only difference between "out" and "ref" is that the compiler forbids reading from an out parameter before it is assigned by the callee, and that the compiler requires assignment before the callee returns normally. Someone who wrote an implementation of this interface in a .NET language other than C# would be able to read from the item before it was initialized, and therefore it could be used as an input. We therefore forbid marking T as "out" in this case. That's regrettable, but nothing we can do about it; we have to obey the type safety rules of the CLR.

Furthermore, the rule of "out" parameters is that they cannot be used for input before they are written to. There is no rule that they cannot be used for input after they are written to. Suppose we allowed

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(); 
    }
}

Once more we have made a cat bark. We cannot allow T to be "out".

It is very foolish to use out parameters for input in this way, but legal.


UPDATE: C# 7 has added in as a formal parameter declaration, which means that we now have both in and out meaning two things; this is going to create some confusion. Let me clear that up:

  • in, out and ref on a formal parameter declaration in a parameter list means "this parameter is an alias to a variable supplied by the caller".
  • ref means "the callee may read or write the aliased variable, and it must be known to be assigned before the call.
  • out means "the callee must write the aliased variable via the alias before it returns normally". It also means that the callee must not read the aliased variable via the alias before it writes it, because the variable might not be definitely assigned.
  • in means "the callee may read the aliased variable but does not write to it via the alias". The purpose of in is to solve a rare performance problem, whereby a large struct must be passed "by value" but it is expensive to do so. As an implementation detail, in parameters are typically passed via a pointer-sized value, which is faster than copying by value, but slower on the dereference.
  • From the CLR's perspective, in, out and ref are all the same thing; the rules about who reads and writes what variables at what times, the CLR does not know or care.
  • Since it is the CLR that enforces rules about variance, rules that apply to ref also apply to in and out parameters.

In contrast, in and out on type parameter declarations mean "this type parameter must not be used in a covariant manner" and "this type parameter must not be used in a contravariant manner", respectively.

As noted above, we chose in and out for those modifiers because if we see IFoo<in T, out U> then T is used in "input" positions and U is used in "output" positions. Though that is not strictly true, it is true enough in the 99.9% use case that it is a helpful mnemonic.

It is unfortunate that interface IFoo<in T, out U> { void Foo(in T t, out U u); } is illegal because it looks like it ought to work. It cannot work because from the CLR verifier's perspective, those are both ref parameters and therefore read-write.

This is just one of those weird, unintended situations where two features that logically ought to work together do not work well together for implementation detail reasons.

余生一个溪 2024-09-09 00:09:49

埃里克·利珀特(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; }
}

这是有效的,因为有两个结构。一种结构获取协方差,另一种结构获取输出参数。 out 参数没有标记为变体,因此编译器很高兴。

Eric Lippert has a great explanation of why this constraint exists.

If you are looking to work around this limitation. You can do it like this:

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; }
}

This works because there are two structures. One structure gets the covariance and the other gets the out parameter. The out parameter is not marked as variant so the compiler is happy.

诺曦 2024-09-09 00:09:49

这意味着你不能有以下声明:

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);

It means you can't have the following declaration:

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

Edit: @Eric Lippert corrected me that this one is still legal:

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

It actually makes sense, since the R generic parameter is not marked as a variant, so it doesn't violate the rule. However, this one is still illegal:

public delegate void MyDelegate<out R, in A>(A arg, out R s);
不知所踪 2024-09-09 00:09:49

但是,可以编译以下代码:

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!
}

But, the following code can be compiled:

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!
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文