开发者

Dynamic Structs are failing

开发者 https://www.devze.com 2023-01-25 17:43 出处:网络
I have a problem using a class of made of structures. Here\'s the basic definition: using System; struct Real

I have a problem using a class of made of structures.

Here's the basic definition:

using System;

struct Real
{
    public double real;

    public Real(double real)
    { 
        this.real = real;
    }
}

class Record
{
    public Real r;
    public Record(double r)
    {
        this.r = new Real(r);
    }
    public void Test(double origval, double newval)
    {
        if (this.r.real == newval)
            Console.WriteLine("r = newval-test passed\n");
        else if (this.r.real == origval)
            Console.WriteLine("r = origval-test failed\n");
        else
            Console.WriteLine("r = neither-test failed\n");
    }
}

When I create a non-dynamic (static?) Record, setting the Real works.

When I create a dynamic Rec开发者_运维问答ord, setting the real doesn't work.

When I create a dynamic Record, replacing the real works.

And here's the test program

class Program
{
    static void Main(string[] args)
    {
        double origval = 8.0;
        double newval = 5.0;

        // THIS WORKS - create fixed type Record, print, change value, print
        Record record1 = new Record(origval);
        record1.r.real = newval;        // change value  ***
        record1.Test(origval, newval);

        // THIS DOESN'T WORK.  change value is not making any change!
        dynamic dynrecord2 = new Record(origval);
        dynrecord2.r.real = newval;     // change value
        dynrecord2.Test(origval, newval);

        // THIS WORKS - create dynamic type Record, print, change value, print
        dynamic dynrecord3 = new Record(origval);
        dynamic r = dynrecord3.r;       // copy out value
        r.real = newval;                // change copy
        dynrecord3.r = r;               // copy in modified value
        dynrecord3.Test(origval, newval);

    }
}

And here's the output: r = newval-test passed r = origval-test failed r = newval-test passed

When I change the struct Real to class Real, all three cases work.

So what's going on?

Thanks,

Max


dynamic is really a fancy word for object as far as the core CLI is concerned, so you are mutating a boxed copy. This is prone to craziness. Mutating a struct in the first place is really, really prone to error. I would simply make the struct immutable - otherwise you are going to get this over and over.


I dug a little deeper into this problem. Here's an answer from Mads Torgersen of Microsoft.


From Mads:

This is a little unfortunate but by design. In

dynrecord2.r.real = newval; // change value

The value of dynrecord2.r gets boxed, which means copied into its own heap object. That copy is the one getting modified, not the original that you subsequently test.

This is a consequence of the very “local” way in which C# dynamic works. Think about a statement like the above – there are two fundamental ways that we could attack that:

1) Realize at compile time that something dynamic is going on, and essentially move the whole statement to be bound at runtime

2) Bind individual operations at runtime when their constituents are dynamic, returning something dynamic that may in turn cause things to be bound at runtime

In C# we went with the latter, which is nicely compositional, and makes it easy to describe dynamic in terms of the type system, but has some drawbacks – such as boxing of resulting value types for instance.

So what you are seeing is a result of this design choice.


I took another look at the MSIL. It essentially takes

dynrecord2.r.real = newval;

and turns it into:

Real temp = dynrecord2.r;
temp.real = newval;

If dynrecord2.r is a class, it just copies the handle so the change affects the internal field. If dynrecord2.r is a struct, a copy is made, and the change doesn't affect the original.

I'll leave it up to the reader to decide if this is a bug or a feature.

Max


Make your struct immutable and you won't have problems.

struct Real
{
    private double real;
    public double Real{get{return real;}}

    public Real(double real)
    { 
        this.real = real;
    }
}

Mutable structs can be useful in native interop or some high performance scenarios, but then you better know what you're doing.

0

精彩评论

暂无评论...
验证码 换一张
取 消