Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Any way to avoid Property inline optimization in C#?

Tags:

c#

stack-trace

So I have a PropertyBag class that is intended to implement INotifyPropertyChanged. In order to make this code work as cleanly as possible and to avoid user error, I am using the stack to get the property name. See, if the property name doesn't match the actual property exactly, then you will have a failure and I am trying to protect from that.

So, here is an example usage of the class:

public class MyData : PropertyBag
{
    public MyData()
    {
        Foo = -1;
    }

    public int Foo
    {
        get { return GetProperty<int>(); }
        set { SetProperty(value); }
    }
}

The important code for the base PropertyBag is here:

public abstract class PropertyBag : INotifyPropertyChanged
{
    protected T GetProperty<T>()
    {
        string propertyName = PropertyName((new StackTrace()).GetFrame(1));
        if (propertyName == null)
            throw new ArgumentException("GetProperty must be called from a property");

        return GetValue<T>(propertyName);
    }

    protected void SetProperty<T>(T value)
    {
        string propertyName = PropertyName((new StackTrace()).GetFrame(1));
        if (propertyName == null)
            throw new ArgumentException("SetProperty must be called from a property");

        SetValue(propertyName, value);
    }

    private static string PropertyName(StackFrame frame)
    {
        if (frame == null) return null;
        if (!frame.GetMethod().Name.StartsWith("get_") &&
           !frame.GetMethod().Name.StartsWith("set_"))
            return null;

        return frame.GetMethod().Name.Substring(4);
    }
}

So now that you have seen my code, I can tell you the problem... In some cases under release build, the "Foo" setter in the "MyData" constructor appears to be getting optimized to inline as SetProperty(-1). Unfortunately, this inline optimization fails out my SetProperty method because I am no longer calling it from a property! FAIL. It appears that I cannot rely on the StackTrace in this way.

Can anyone A: Figure out a better way to do this but still avoid passing in "Foo" to GetProperty and SetProperty?
B: Figure out a way to tell the compiler to not optimize in this case?

like image 329
Brian Genisio Avatar asked Feb 17 '09 16:02

Brian Genisio


3 Answers

Using the stack here is slow and unnecessary; I would simply use:

get { return GetProperty<int>("Foo"); }
set { SetProperty("Foo", value); }

(hint: I've done a lot of work with custom property models; I know that this works well...)

Another alternative is an object key (use reference equality to compare) - a lot of ComponentModel works this way, as do some of the properties in WF/WPF:

static readonly object FooKey = new object();
...
get { return GetProperty<int>(FooKey); }
set { SetProperty(FooKey, value); }

Of course, you could declare a type for the keys (with a Name property), and use that:

static readonly PropertyKey FooKey = new PropertyKey("Foo");

etc; however, to answer the question: mark it (but don't do this) with:

[MethodImpl(MethodImplOptions.NoInlining)]

or

[MethodImpl(MethodImplOptions.NoOptimization)]

or

[MethodImpl(MethodImplAttributes.NoOptimization
    | MethodImplAttributes.NoInlining)]

like image 106
Marc Gravell Avatar answered Sep 27 '22 23:09

Marc Gravell


Using the stack is not a good idea. You are relying on internal implementation of the compiler to artificially tie in your property bag to the language properties.

  1. having a requirement to add the MethodImpl attribute makes the use of your property bag non-transparent for other developers.
  2. even if the property bag has the MethodImpl attribute, nothing guarantees you it will be the first frame on the call stack. It is possible that the assembly was instrumented or modified to inject calls between the actual property and the call to your property bag. (Think aspect programming)
  3. New languages or even a future version of the C# compiler might decorate the property accessors in a different way then '_get' and '_set'
  4. Constructing the call stack is relatively slow operation, as it requires the internal compressed stack to be decompressed and the name of each type and method to be obtained using reflection.

You should really just implement your property bag accessors to take a parameter to identify the property - either a string name (like Hastable) or an object (like the WPF dependency property bag)

like image 38
Franci Penov Avatar answered Sep 27 '22 22:09

Franci Penov


Try the new [CallerMemberName] attribute.

Place it on a parameter to your method ([CallerMemberName] callerName = null) and the compiler will rewrite all calls to your method to pass the caller name automatically (your calls don't pass the parameter at all).

It doesn't eliminate any optimizations, and is much faster than lambdas or reflection or stacks, and works in Release mode.

P.S. if CallerMemberNameAttribute doesn't exist in your version of the framework, just define it (empty). It's a language feature, not a framework feature. When the compiler sees [CallerMemberNameAttribute] on a parameter, it just works.

like image 41
Chris Bordeman Avatar answered Sep 27 '22 21:09

Chris Bordeman