Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

StackFrame behaving differently in release mode

Tags:

c#

.net

Here is my code:

public class UserPreferences
{
    /// <summary>
    /// The EMail signature.
    /// </summary>
    [UserPreferenceProperty(Category = "Email", DefaultValue = "My default value")]
    public static string Signature
    {
        get
        {
            return UserPreferenceManager.GetValue();
        }

        set
        {
            UserPreferenceManager.SetValue(value);
        }
    }
}

public static string GetValue()
{
    if (((VTXPrincipal)Thread.CurrentPrincipal).VTXIdentity.OperatorID == null)
    {
        throw new Exception("Missing Operator ID");
    }

    string value = string.Empty;

    var frame = new StackFrame(1);  ***** <------ problem here.....

    var property = frame.GetMethod();
    var propertyname = property.Name.Split('_')[1];
    var type = property.DeclaringType;   ***** <------ problem here.....
    if (type != null)
    {
        var userPreference = typeof(UserPreferences).GetProperty(propertyname).GetCustomAttributes(true).FirstOrDefault() as UserPreferencePropertyAttribute;

        if (userPreference != null)
        {
            string category = userPreference.Category;
            string description = propertyname;
            value = GetValue(category, description, ((VTXPrincipal)Thread.CurrentPrincipal).VTXIdentity.OperatorID);
            if (value == null)
            {
                // always return something
                return userPreference.DefaultValue;
            }
        }
        else
        {
            throw new Exception("Missing User Preference");
        }
    }

    return value;
}

Inside the GetValue method, StackFrame works differently in release mode vs. debug mode.

In debug mode, I correctly get the property name as signature

But in Release mode, property name is GetUserPreferenceValueTest because this is the test method that makes the calls as clients.

There fore my code works in debug mode but fails in release mode.

Q. How can I use StackFrame properly so it works in Debug vs. Release modes. 

Q. Is there any other way to get calling property name and related information at run time?
like image 677
dotnet-practitioner Avatar asked Dec 28 '12 23:12

dotnet-practitioner


2 Answers

Assuming your problem survives the discussion of whether you could just use another library rather than rolling your own... if you find yourself using C# 5 &.NET 4.5, take a look at the CallerMemberName attribute. With CallerMemberName you can modify your GetValue() method signature to be

public static string GetValue([CallerMemberName] string callerName = "")

The property can then call GetValue() with no parameter and you'll get the property name passed into GetValue() as you want.

like image 26
Curt Nichols Avatar answered Oct 18 '22 22:10

Curt Nichols


I answered a similar question once, please read my answer here.

In short, this is a very bad design decision because your method is a hypocrite—it talks different to different callers but doesn't tell it in open. Your API should never, ever rely on who calls it. Also, the compiler can break the stack trace in an unexpected way due to language features like lambdas, yield and await, so even if this worked in Release mode, it would certainly break some day.

You're effectively building a complex indirection mechanism instead of using language feature designed for passing information to methods—method parameters.

Why do you use attributes? Do you read them elsewhere?

If you do, and you don't want to repeat "Email" both as parameter to GetValue call and attribute value, you may consider passing a property Expression<> to GetValue, which will extract the attribute. This is similar to your solution, but it is explicit:

[UserPreferenceProperty(Category = "Email", DefaultValue = "My default value")]
public string Signature
{
    get { return GetValue (prefs => prefs.Signature); }
    set { SetValue (prefs => prefs.Signature, value); }
}

This answer shows how to implement this.

I see you are checking Thread.CurrentPrincipal in your code. Again, this is not a really good practice because it is not obvious to client code that accessing a property can result in an exception. This is going to be a debugging nightmare for someone who supports your code (and trust me, your code may run for years in production, long after you move onto another project).

Instead, you should make VTXIdentity a parameter to your settings class constructor. This will ensure the calling code knows you enforce security on this level and by definition knows where to obtain this token. Also, this allows you to throw an exception as soon as you know something is wrong, rather than when accessing some property. This will help maintainers catch errors earlier—much like compile errors are better than runtime errors.

Finally, while this is a fun exercise, there are plenty performant and tested solutions for storing and reading configuration in C#. Why do you think you need to reinvent the wheel?

like image 141
Dan Abramov Avatar answered Oct 18 '22 23:10

Dan Abramov