鉴于以下代码:
using System;
class MyClass
{
    public MyClass x;
}
public static class Program
{
    public static void Main()
    {
        var a = new MyClass();
        var b = new MyClass();
        a.x = (a = b);
        Console.WriteLine(a.x == a);
    }
}
Run Code Online (Sandbox Code Playgroud)
前两行很明显,只是两个不同的对象。
我假设第三行执行以下操作:
(a = b)分配b给a并返回b,因此现在a等于b。a.x分配给b。这意味着,a.x等于b,也b等于a。这意味着a.x等于a。
但是,代码打印False.
这是怎么回事?
Mag*_*ron 20
发生这种情况是因为您试图a在同一个语句中更新两次。aina.x=指的是旧实例。因此,您要更新a为 reference ,b而将旧的a对象字段更新x为 reference b。
您可以通过以下方式确认:
void Main()
{
    var a = new MyClass(){s="a"};
    var b = new MyClass() {s="b"};
    var c =a;
    a.x = (a=b);
    Console.WriteLine($"a is {a.s}");
    Console.WriteLine(a.x == b);
    Console.WriteLine($"c is {c.s}");       
    Console.WriteLine(c.x == b);
}
class MyClass
{
    public MyClass x;
    public string s;
}
Run Code Online (Sandbox Code Playgroud)
答案将是:
a is b
False
c is a
True
Run Code Online (Sandbox Code Playgroud)
编辑:只是为了更清楚一点,这与运算符的执行顺序无关,而是因为同一语句中同一变量中的两次更新。分配(a=b)在 之前执行a.x=,但这无关紧要,因为a.x引用的是旧实例,而不是新更新的实例。正如@Joe Sewell 的回答所解释的那样,这种情况会发生,因为找到分配目标的评估是从左到右的。
Joe*_*ell 16
In a.x = (a = b), the left hand side a.x is evaluated first to find the assignment target, then the right hand side is evaluated.
This was also surprising to me, because I intuitively think it starts on the rightmost side and evaluates leftward, but this is not the case. (The associativity is right-to-left, meaning the parentheses in this case are not needed.)
Here's the specification calling out the order things happen in, with the relevant bits quoted below:
The run-time processing of a simple assignment of the form
x = yconsists of the following steps:
- If
 xis classified as a variable:
xis evaluated to produce the variable.yis evaluated and, if required, converted to the type ofxthrough an implicit conversion.- [...]
 - The value resulting from the evaluation and conversion of
 yis stored into the location given by the evaluation ofx.
Looking at the IL generated by the sharplab link Pavel posted:
        // stack is empty []
newobj instance void MyClass::.ctor()
        // new instance of MyClass on the heap, call it $0
        // stack -> [ref($0)]
stloc.0
        // stack -> []
        // local[0] ("a") = ref($0)
newobj instance void MyClass::.ctor()
        // new instance of MyClass on the heap, call it $1
        // stack -> [ref($1)]
stloc.1
        // stack -> []
        // local[1] ("b") = ref($1)
ldloc.0
        // stack -> [ref($0)]
ldloc.1
        // stack -> [ref($1), ref($0)]
dup
        // stack -> [ref($1), ref($1), ref($0)]
stloc.0
        // stack -> [ref($1), ref($0)]
        // local[0] ("a") = ref($1)
stfld class MyClass MyClass::x
        // stack -> []
        // $0.x = ref($1)
Run Code Online (Sandbox Code Playgroud)
        Just to add some IL fun into the discussion:
The Main method header looks next way:
method private hidebysig static void
    Main() cil managed
  {
    .maxstack 3
    .locals init (
      [0] class MyClass a,
      [1] class MyClass b
    )
Run Code Online (Sandbox Code Playgroud)
The a.x = (a=b); statement is translated to the next IL:
IL_000d: ldloc.0      // a
IL_000e: ldloc.1      // b
IL_000f: dup
IL_0010: stloc.0      // a
IL_0011: stfld        class MyClass::x
Run Code Online (Sandbox Code Playgroud)
First two instructions load (ldloc.0, ldloc.1) onto evaluation stack references stored in a and b variables, lets call them aRef and bRef, so we have next evaluation stack state:
bRef
aRef
Run Code Online (Sandbox Code Playgroud)
The dup instruction copies the current topmost value on the evaluation stack, and then pushes the copy onto the evaluation stack:
bRef
bRef
aRef
Run Code Online (Sandbox Code Playgroud)
The stloc.0 pops the current value from the top of the evaluation stack and stores it in a the local variable list at index 0 (a variable is set to bRef), leaving stack in next state:
bRef
aRef
Run Code Online (Sandbox Code Playgroud)
And finally stfld poppes from the stack the value (bRef) and the object reference/pointer (aRef). The value of field in the object (aRef.x) is replaced with the supplied value (bRef). 
Which all result in the behavior described in the post, with both variables (a and b) pointing to the bRef with bRef.x being null and aRef.x pointing to bRef, which can be checked with extra variable containing aRef as @Magnetron suggested.