tags:

views:

100

answers:

4

Edit: I'm well aware of that this works very well with value types, my specific question is about using this for reference types.

Edit2: I'm also aware that you can't overlay reference types and value types in a struct, this is just for the case of overlaying several reference type fields with each other.

I've been tinkering around with structs in .NET/C#, and I just found out that you can do this:

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication1 {

    class Foo { }
    class Bar { }

    [StructLayout(LayoutKind.Explicit)]
    struct Overlaid {
        [FieldOffset(0)] public object AsObject;
        [FieldOffset(0)] public Foo AsFoo;
        [FieldOffset(0)] public Bar AsBar;
    }

    class Program {
        static void Main(string[] args) {
            var overlaid = new Overlaid();
            overlaid.AsObject = new Bar();
            Console.WriteLine(overlaid.AsBar);

            overlaid.AsObject = new Foo();
            Console.WriteLine(overlaid.AsFoo);
            Console.ReadLine();
        }
    }
}

Basically circumventing having to do dynamic casting during runtime by using a struct that has an explicit field layout and then accessing the object inside as it's correct type.

Now my question is: Can this lead to memory leaks somehow, or any other undefined behavior inside the CLR? Or is this a fully supported convention that is usable without any issues?

I'm aware that this is one of the darker corners of the CLR, and that this technique is only a viable option in very few specific cases.

A: 

I'm not aware of any issues with it. Furthermore, I doubt that Microsoft would permit this usage if it was dangerous in a non-obvious way.

Matt Howells
Which is my assumption also, but it feels like one of the very dark corners of the CLR and I want it assured by someone that is a 100% on it.
thr
A: 

I can't see how the explicit-layout version can be verifiable without the runtime injecting extra checks anyway, since it allows you to see a non-null reference to something that isn't of the declared type.

This would be safer:

struct Overlaid { // could also be a class for reference-type semantics
    private object asObject;
    public object AsObject {get {return asObject;} set {asObject = value;} }
    public Foo AsFoo { get {return asObject as Foo;} set {asObject = value;} }
    public Bar AsBar { get {return asObject as Bar;} set {asObject = value;} }
}

No risk of torn references etc, and still only a single field. It doesn't involve any risky code, etc. In particular, it doesn't risk something silly like:

    [FieldOffset(0)]
    public object AsObject;
    [FieldOffset(0)]
    public Foo AsFoo;
    [FieldOffset(1)]
    public Bar AsBar; // kaboom!!!!

Another issue is that you can only support a single field this way unless you can guarantee the CPU mode; offset 0 is easy, but it gets trickier if you need multiple fields and need to support x86 and x64.

Marc Gravell
Didn't really answer my question. I wanted to know if it was possible to do what I asked (and _exactly_ what I asked). Not another solution to the same problem (because while my example here is simplified, the real problem can not use a reference type).
thr
Care to explain the downvote? That is a **seriously** risky approach; just some brief cursory tests shows things like `System.SystemException` - that does **not** sound like something you want to introduce! Even as a **risk**. (message descriptions like ""Cannot find the method on the object instance.")
Marc Gravell
@thr - then change "class" to "struct". That wasn't the important bit of the post; here, I'll change it for you...
Marc Gravell
I need to keep them in the exact same memory location. You did simply not answer my question. Sure you provided another solution, but that wasn't my question: "How do I solve this in a different way?", now was it?
thr
I also stated that I know how stupid this is and how exotic the few possible use cases are. And I have one of those use cases.
thr
Your version also forces a dynamic cast, which is what I wanted to circumvent in the first place (read your code more carefully now)
thr
@thr - count the fields in the type; there is exactly one field. How is that **not** "the exact same memory location"? In case the auto-prop is confusing, I'll unwrap it.
Marc Gravell
Yes as I said, I saw that (read your code more carefully) - but your version forces a dynamic cast and it's associated type check, which I can't afford. I even say that in the original question: "Basically circumventing having to do dynamic casting during runtime by using a struct that has an explicit field layout and then accessing the object inside as it's correct type."
thr
@thr - what makes you think you can't afford that? I'm no stranger to performance code (I'm currently using ILGenerator to do *verifiable* optimisations that the C# compiler can't do), and a type-check (`isinst`) is *not* your biggest problem. Producing code that doesn't do stupid things (see `SystemException`) is far more important.
Marc Gravell
Because I have benchmarked it and it's to slow?
thr
@thr - run it through PEVerify; it is **not** verifiable. So take your chances. On your head be it. You asked for possible problems; I gave you an alternative, and some demonstrably valid things to watch (`SystemException` and `"Type load failed"`). But thanks for the negativity.
Marc Gravell
I did not ask for alternatives, because there is no alternative for what I want to do (performance wise).
thr
@thr - my bad; I changed the phrasing on that.
Marc Gravell
+1  A: 

If you align the type in an unsafe way, the runtime will throw a TypeLoadException on load even when compiling with /unsafe. So I think you are safe.

I'm guessing--since you can use StructLayout and compile your code without /unsafe flags-- that this is a feature of the CLR. You need the StructLayout attribute simply because C# has no direct means of declaring types this way.

Take a look at this page which details some of the way C# structs translates into IL, you'll notice that there are many memory layouts support built-in to the IL/CLR itself.

chakrit
Which is what I'm assuming also, I just want it confirmed by someone that really knows the CLR that this can not, in any way, cause me to create memory leaks or something other extremely undefined behavior.
thr
+1  A: 

Well, you found a loop hole, the CLR permits it since all overlapped fields are objects. Anything that would allow you to mess with an object reference directly gets rejected with a TypeLoadException:

  [StructLayout(LayoutKind.Explicit)]
  struct Overlaid {
    [FieldOffset(0)]
    public object AsObject;
    [FieldOffset(0)]
    public IntPtr AsPointer;
  }

But you can exploit it by giving the classes fields. Nothing really bad happens as long as you are just reading the field values, you can get the value of the tracking handle that way for example.

Writing those fields however leads to an ExecutionEngineException. I think however that it is an exploit if you can guess the value of a tracking handle correctly. Practical use is sufficiently close to zero though.

Hans Passant
If I understand you correctly: As long as all the overlapping fields are objects, it's safe to both read and write to them using the correct type (Basically assigning to the `AsObject` field and then reading from the `AsFoo`/`AsBar` field depending on the type)? If I try to read a `Foo` from `AsBar` it will of course break.It can not give me any memory leaks, etc. in any way?
thr
@thr: no, the garbage collector won't have a problem with it.
Hans Passant