Searock
Searock

Reputation: 6478

Method invocation on a struct?

When we invoke a method on a object, then the reference of the object is passed implicitly to the method.

So my question is what happens when a method is invoked on a struct ? Is it similar to classes in this aspect?

Upvotes: 43

Views: 13079

Answers (3)

Florian Greinacher
Florian Greinacher

Reputation: 14784

According to the CIL spec the this pointer is passed by reference / as managed pointer. As Binary Worrier assumed this is a CIL feature.

Instance and virtual methods of classes shall be coded to expect a reference to an instance of the class as the this pointer. By contrast, instance and virtual methods of value types shall be coded to expect a managed pointer (see Partition I) to an unboxed instance of the value type. The CLI shall convert a boxed value type into a managed pointer to the unboxed value type when a boxed value type is passed as the this pointer to a virtual method whose implementation is provided by the unboxed value type.

So from a high-level perspective a call to an instance method of a reference type (class) looks like this:

MyClass myClass = MyClass_Constructor();

MyClass_MyInstanceMethod(myClass, myFirstParameter);
//                       ^
//                       The "this" argument

And a call to an instance method of a value type (struct) like this:

MyStruct myStruct = MyStruct_Constructor();

MyStruct_MyInstanceMethod(ref myStruct, myFirstParameter);
//                        ^
//                        The "this" argument

Upvotes: 39

Eric Lippert
Eric Lippert

Reputation: 660138

Florian is right; a few more details while we're on the subject:

When we invoke a method on a object, then the reference of the object is passed implicitly to the method.

Correct. One way to think about this is that a method call like:

class C
{
    int y;
    public void M(int x) 
    { 
        Console.WriteLine(x + y); 
    }
}
...
C c = new C();
c.M(10);

is actually the same as

class C
{
    int y;
    public static void M(C _this, int x) 
    { 
        Console.WriteLine(x + _this.y); 
    }
}
...
C c = new C();
C.M(c, 10);

That is, every instance method has a hidden "this" parameter, and the method is "really" static.

So my question is what happens when a method is invoked on a struct ? Is it similar to classes in this aspect?

Yes. Instead of a reference to an instance, what is passed is an alias to the variable that contains the struct. That's how struct methods can mutate the struct. (Of course, mutating a struct is a bad practice, but sometimes necessary.)

struct S
{
    int y;
    public void M(int x) 
    { 
        Console.WriteLine(x + y); 
    }
}
...
S s = new S();
s.M(10);

is logically the same as

struct S
{
    int y;
    public static void M(ref S _this, int x) 
    { 
        Console.WriteLine(x + _this.y); 
    }
}
...
S s = new S();
S.M(ref s, 10);

An interesting question then arises: what if the "receiver" is not a variable? (*) You can only take a ref to a variable. Suppose you have:

GetAnS().M(10); 

??? What happens then?

We make a variable for you. That becomes

S temporary = GetAnS();
temporary.M(10);

and now the receiver is a variable, so we can make the "hidden this parameter" an alias to it.

(*) There are other interesting cases here as well, like what if the struct is mutable but the variable is readonly, and so on.

Upvotes: 32

thecoop
thecoop

Reputation: 46108

I wrote a series of blog posts on exactly this subject a while back. In particular, http://www.simple-talk.com/community/blogs/simonc/archive/2010/11/02/95489.aspx

In IL, you can store a managed pointer on the stack, which is like an object reference, except it can point at things other than object references. There are specific instructions to get a managed pointer to something, such as ldloca, which gets a pointer to a local variable, or ldelema, which gets a pointer to a particular element in an array.

These are safe pointers, as the garbage collector knows about them, and so changes them if the object you're pointing to is moved by the GC.

Method calls on structs require a managed pointer to the struct (which can be anywhere in memory - on the stack, in an argument list, in another object on the heap) the method is to be executed on as the this pointer. Method calls on reference types require an object reference.

This causes some issues when calling methods on generic types, which can be either value or reference types. I have a look at that in later blog posts.

One of the side effects of being able to use managed pointers to value types is that it is possible in IL to call a method on a boxed value type without having to unbox it:

// you have a struct on the stack
box MyStructType    // this copies the value on the stack to an object on the heap
unbox MyStructType  // this returns a managed pointer to the value type instance in its boxed form. It doesn't copy the value itself back to the stack (despite the name)
call instance void MyStructType::MyMethod()

Upvotes: 2

Related Questions