Set C# Property that has no Setter

2019-07-09 05:05发布

问题:

In C#, there is the Exception class. For the purposes of testing, I would like to be able to set its StackTrace property to arbitrary strings. StackTrace has no setter, so my first attempt was to try using reflection:

Exception instance = new Exception("Testing");
PropertyInfo propertyInfo = typeof(Exception).GetProperty("StackTrace");
propertyInfo.SetValue(instance, "Sample StackTrace value", null);

This yields the runtime error:

System.ArgumentException : Property set method not found.

Is there any way I could set the StackTrace property? More generally, is there a way to set a property that lacks a setter?

回答1:

You could derive your own exception class and override the StackTrace property, which is virtual:

public sealed class MyException: Exception
{
    public MyException(string message, string stackTrace): base(message)
    {
        _stackTrace = stackTrace;
    }

    public override string StackTrace
    {
        get
        {
            return _stackTrace;
        }
    }

    private readonly string _stackTrace;
}

Note that to do this properly, you should really implement all the standard exception constructors, but for unit testing this might not be strictly necessary.

See Designing Custom Exceptions for more details:



回答2:

That property is implemented this way:

public virtual string StackTrace
{
    [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    get
    {
        return this.GetStackTrace(true);
    }
}

And that called method is implemented this way:

private string GetStackTrace(bool needFileInfo)
{
    string text = this._stackTraceString;
    string text2 = this._remoteStackTraceString;
    if (!needFileInfo)
    {
        text = this.StripFileInfo(text, false);
        text2 = this.StripFileInfo(text2, true);
    }
    if (text != null)
    {
        return text2 + text;
    }
    if (this._stackTrace == null)
    {
        return text2;
    }
    string stackTrace = Environment.GetStackTrace(this, needFileInfo);
    return text2 + stackTrace;
}

So it seems that if you set the field _stackTraceString to any String, you will get _stackTraceString + _remoteStackTraceString.

You can set fields with FieldInfo.SetValue.

I got this information using http://ilspy.net.

Do not do this on production. Things are designed in a specific way for a reason, just the exposed API is guaranteed, a minor upgrade could change this internal implementation detail and break your code, so never rely on internal details, just exposed APIs.

Cheers.



回答3:

You can modify the backing field of the object:

Exception instance = new Exception("Testing");

var f = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.SetField);

f.SetValue(instance, "hello");

This should work, to get a list of private fields you can use:

var fields = typeof(Exception).GetFields(BindingFlags.Instance | BindingFlags.NonPublic);

Personally I prefer the solution proposed by Matthew Watson.



回答4:

The StackTrace property of an Exception object is set by the runtime during stack walk, so we cannot set it manually.



标签: c# reflection