设置没有Setter的C#属性

3
在C#中,有一个名为Exception的类。为了测试目的,我希望能够将其StackTrace属性设置为任意字符串。由于StackTrace没有setter方法,因此我的第一次尝试是试图使用反射:
Exception instance = new Exception("Testing");
PropertyInfo propertyInfo = typeof(Exception).GetProperty("StackTrace");
propertyInfo.SetValue(instance, "Sample StackTrace value", null);

这会产生运行时错误:
System.ArgumentException:未找到属性设置方法。
有没有办法设置StackTrace属性?更一般地说,有没有一种方法可以设置缺少setter的属性?

3
您可以使用 Reflector 查找此属性的后备变量名称,然后使用反射来设置它。 - undefined
如何扩展你自己的异常类? - undefined
@Nick - 没有后备字段:return this.GetStackTrace(true); - undefined
实际上有一个后备字段。@Nick在他的断言中是正确的:_stackTraceString可以通过反射进行设置。 - undefined
4个回答

10

您可以派生自己的异常类并覆盖虚拟的StackTrace属性:

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;
}

请注意,为了正确地实现这一点,您应该真正实现所有标准异常构造函数,但对于单元测试来说,这可能并非严格必要。

有关更多详细信息,请参见设计自定义异常


一个不错的解决方案,但是覆盖所有异常来使用自定义异常类是不可行的。 - undefined
也许你还可以编写一个包装异常类,专门用于测试目的。 - undefined
没错, 在测试库中创建自己的异常类(仅限于该库内部)可能比使用反射更好。 - undefined
C#中使用_paramName作为变量名是正常的吗?O.o - undefined
@mmcrae 这在私有字段中非常常见。然而,通过输入 "_paramName",你让它看起来像是你在考虑参数名字 - 对于参数来说,这绝对不正常。我发布的代码将其用于私有字段,而不是参数。有些人使用 this.,如果使用 _,可以争论将其用作后缀。 - undefined

2

该属性的实现方式如下:

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

这个被调用的方法是这样实现的:

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;
}

看起来,如果你将字段_stackTraceString设置为任何String,你会得到_stackTraceString+_remoteStackTraceString
您可以使用FieldInfo.SetValue设置字段。
我使用http://ilspy.net获得了这些信息。 不要在生产环境中这样做。事情是有特定原因设计的,只有暴露的API是保证的,小升级可能会改变这个内部实现细节并破坏你的代码,所以永远不要依赖内部细节,只依赖于暴露的API。
干杯。

2
您可以修改对象的后备字段:
Exception instance = new Exception("Testing");

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

f.SetValue(instance, "hello");

这应该有效,要获取私有字段列表,您可以使用以下方法:

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

就个人而言,我更喜欢Matthew Watson提出的解决方案。


0
Exception对象的StackTrace属性是在运行时通过堆栈遍历设置的,因此我们无法手动设置它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接