a bit like logging of intermediate results but being programmatically available to the caller and optional
When you describe it like that, there's three possible bells that ring in my head:
- Simple logging
- Pub/sub
- Callback/continuation methods
Since you describe "passing additional parameters all the way through" as a negative, that excludes using callback or continuation methods. Since you want the findings to be programmatically available, that excludes directly logging your findings to a log file. So we're left with a pub/sub system.
Pub/sub (or event-driven, which in this case is synonymous) systems allow for consumers to optionally subscribe to information. Have your object write its findings to an output feed, and give the consumer the option of subscribing to that feed. If not subscribed, nothing happens. If subscribed, the behavior you define will execute at the appropriate time.
Since your scenario is a small scale implementation, a simple event handler suffices here.
Note that for this example, the method is assumed to be int DoFoo(int)
and the related event is therefore called FooDone
. Adjust these names to fit your use case.
First, define your findings as event args, and add the properties to house the data that you want to pass. For this example, I assume that the input, output and a string-based explanation suffice.
public class FooDoneEventArgs : EventArgs
{
public int InputValue { get; set; }
public int OutputValue { get; set; }
public string Reason{ get; set; }
}
Next, give your object an event handler that publishes its findings using these event args.
public class FooService
{
public int DoFoo(int input)
{
int result;
string reason;
if(input > 5)
{
result = 123;
reason = "the input is bigger than five";
}
else
{
result = 456;
reason = "the input is smaller than or equal to than five";
}
OnFooDone(input, result, reason);
return result;
}
public event EventHandler<FooDoneEventArgs> FooDone;
public virtual void OnFooDone(int input, int output, string reason)
{
var handler = this.FooDone;
if(handler != null)
{
handler(this, new FooDoneEventArgs()
{
InputValue = input,
OutputValue = output,
Reason = reason
});
}
}
}
Thirdly, your service's consumer can optionally subscribe to this event:
public class Consumer
{
private readonly FooService _fooService;
public Consumer(FooService fooService)
{
_fooService = fooService;
_fooService.FooDone += ReportFindings;
}
static void ReportFindings(object sender, FooDoneEventArgs e)
{
Console.WriteLine($"Inputting {e.InputValue} resulted in {e.OutputValue}, because {e.Reason}");
}
public void MyMethod()
{
// Your code that in some way uses the `DoFoo()` method, e.g.
int result = _fooService.DoFoo(7);
}
}
If you don't want your consumer to subscribe to the findings, just don't subscribe to the event. You can also make this a dynamic decision, only subscribing to the event when needed. The world is your oyster here.
I do want to take a moment here to point out that if the only goal is to log these findings so the developer can review them at their own leisure, then I would suggest you relax/scrap your "programmaticaly available" requirement, because logging your findings directly to a logger is significantly easier than setting up an event feed and having to programmatically handle the findings, especially if this kind of behavior repeats across your codebase.
When using logging, the optional nature of the logging can be implemented using log levels, which most if not all logging libraries natively support. In short, you simply register your findings at a certain level (e.g. trace seems the most appropriate level here), and then you can configure your logs and tell them to either do or don't capture log messages with a trace level.
You could, as is most commonly done, not capture trace logging by default, but if you ever need to investigate an issue in a deployed release, you can enable the capturing of trace logging temporarily, capturing the messages so you can understand what went on during the runtime. When the investigation is done, you revert back to no longer capturing the trace log messages.
For this, I suggest you look at any established logging library. It's hardly worth reinventing the wheel here.