Compartilhar via


Programmatically Enabling a Switch

While testing a project I was working on today, I wanted to change the level of a Switch used in one of the framework assemblies.  Since I wasn't creating the instance of the Switch-derived class used in that code, the only built-in option available to me was to set the value in my configuration file, something like:

<

system.diagnostics>
        <switches>
            <add name="XmlSerialization.Compilation" value="4"/>
        </switches>
</system.diagnostics>

That's all fine and dandy, but what I really wanted to do was control this programmatically so that I could enable it and disable it from different sections of my code.  In the .NET Framework 1.x there's nothing related to Switch that would allow me to do this, so I resorted to using reflection (I'd recommend this approach only for exploratory purposes and not for any real live production code since there are no guarantees the implementation of the Switch classes will remain the same in future versions of the framework.  Note, too, that there's a better solution in the .NET Framework 2.0.)

Ideally, I wanted to be able to do something like the following:

    using(new ForceSwitch("XmlSerialization.Compilation", TraceLevel.Verbose))
{
        XmlSerializer xs = new XmlSerializer(typeof(theType));
xs.Serialize(Console.Out, theData);
}

That way, no matter what the value was in the configuration file (or if it was even configured at all), I could be sure that the switch's level for that section of code was set to Verbose.  When the using's scope ended, the settings would be reverted to the way they were originally.

The following is what I came up with:

    internal struct ForceSwitch : IDisposable
{
        private bool _disposed;
        private string _traceName;
        private Hashtable _switchSettings;
        private bool _wasEnabled;
        private TraceLevel _wasLevel;

public ForceSwitch(string name, TraceLevel level)
{
// Force initialization of diagnostics configuration section (by getting the level of a switch)
string dummyName = "dummy" + Guid.NewGuid().ToString("N");
TraceLevel ignored = new TraceSwitch(dummyName, dummyName).Level;

// Get the switch settings hashtable, a private static member of Switch
Hashtable ht = (Hashtable)typeof(Switch).GetField("switchSettings", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);

// Store info so we can revert back when we're done
_switchSettings = ht;
_traceName = name;
_wasEnabled = _switchSettings.ContainsKey(_traceName);
_wasLevel = _wasEnabled ? (TraceLevel)_switchSettings[_traceName] : TraceLevel.Off;
_disposed = false;

// Force trace value by setting it in the hashtable
ht[_traceName] = (int)level;
}

public void Dispose()
{
// Clean up after the switch
if (!_disposed)
{
if (_wasEnabled) _switchSettings[_traceName] = (int)_wasLevel;
else _switchSettings.Remove(_traceName);
_disposed = true;
}
}
}

How does it work?  In the constructor, I first force the initialization of the Switch class' internal static state by attempting to retrieve the level of a new switch.  In order to determine the initial level for a switch, the Switch class needs to ensure that it's loaded the configuration data, which it does using the configuration section handler for system.diagnostics.  In doing so, it creates a Hashtable that stores all of the switch settings from the config file.  Once I know it's been initialized, I can use reflection to retrieve a reference to that Hashtable.  I then maintain some state to allow me to revert back to the original state when I'm Dispose'd, and I set the desired TraceLevel for the specific switch in the Hashtable.  From then on, until my Dispose method is called, any new Switch-derived objects created will find the value I've set (note that this only works for new switches and not ones that are already initialized).

Certainly not a perfect approach, but it got the job done.

Comments