DanielCardin
DanielCardin

Reputation: 555

C# How to tell if an object implements a particular method

So I have a number of different potential object that can output data (strings). What I want to be able to do, is to Run a generic Output.WriteLine function, with the potential arguments that define where you want it to be outputted to. What I've got for code -

//Defined in static class Const
public enum Out : int { Debug = 0x01, Main = 0x02, Code = 0x04 };

static class Output
{
    private static List<object> RetrieveOutputMechanisms(Const.Out output)
    {
        List<object> result = new List<object>();

    #if DEBUG
        if (bitmask(output, Const.Out.Debug))
            result.Add(1);//Console); //I want to add Console here, but its static
    #endif
        if (bitmask(output, Const.Out.Main))
            if (Program.mainForm != null)
                result.Add(Program.mainForm.Box);

        if (bitmask(output, Const.Out.Code))
            if (Program.code!= null)
                result.Add(Program.code.Box);

        return result;
    }

    public static void WriteLine(Color color, string str, Const.Out output = Const.Out.Debug & Const.Out.Main)
    {
        Console.WriteLine(
        List<object> writers = RetrieveOutputMechanisms(output);
        foreach (object writer in writers)
            writer.WriteLine(str, color);
    }
}

The point of this, is that the output destinations are not always existent, as they are on forms that may or may not exist when these calls are called. So the idea is to determine which ones you're trying to print to, determine if it exists, add it to the list of things to be printed to, then loop through and print to all of them if they implement the "WriteLine" method.

The two problems that I've come across, are

  1. That Console is a static class, and can't properly (as far as my knowledge goes) be added to the object list.
  2. I don't know how I can assert that the objects in the list define WriteLine, and cast them to something that would apply to more than one base Type. Assuming I can get Console to work properly in this scheme, that would be the obvious problem, its not of the same base type as the actual Boxes, but also, if I had something that wasnt a Box, then it would be lovely to do something like

    foreach (object writer in writers) .WriteLine(str, color)

so that I wouldn't have to individually cast them.

The bigger reason that I don't simply WriteLine from the RetrieveOutputMechanisms function, is that I want this to cover more than just WriteLine, which means that I would need to copy the bitmask code to each function.

EDIT: I realise that adding public properties to Program is a bad idea, if you know how I can avoid it (the necessity coming from needing to be able to access any WriteLine-able form objects that come and go, from anywhere), by all means please elaborate.

Upvotes: 0

Views: 282

Answers (3)

Kevin Brock
Kevin Brock

Reputation: 8944

One way would be to use an Action (a delegate) and store those in your List. This will work for Console and any other class as you can easily write a lambda (or a 2.0 delegate) to map your output variables to the right parameters in the called method. There will be no need for casting. It could work something like this:

(This assumes you are using C# 3.5 or later but you can do all this in anything from 2.0 and on using delegates)

static class Output
{
    private static List<Action<string, Color>> RetrieveOutputMechanisms(Const.Out output)
    {
        List<Action<string, Color>> result = new List<string, Color>();

    #if DEBUG
        if (bitmask(output, Const.Out.Debug))
            result.Add((s, c) => Console.WriteLine(s, c)); //I want to add Console here, but its static
    #endif
        if (bitmask(output, Const.Out.Main))
            if (Program.mainForm != null)
                result.Add((s, c) => Program.mainForm.Box.WriteLine(s, c));

        if (bitmask(output, Const.Out.Code))
            if (Program.code!= null)
                result.Add((s, c) => Program.code.Box.WriteLine(s, c));

        return result;
    }

    public static void WriteLine(Color color, string str, Const.Out output = Const.Out.Debug & Const.Out.Main)
    {
        var writers = RetrieveOutputMechanisms(output);
        foreach (var writer in writers)
            writer(str, color);
    }
}

(edit to add)

You could change this more significantly to allow classes to "register" to be able to do the writing for a specific "output mechanism" in the Output class itself. You could make Output a singleton (there are arguments against doing that but it would be better than sticking public static variables in your main program for this purpose). Here is an example with more significant changes to your original class:

public sealed class Output
{
    private Dictionary<Out, Action<string, Color>> registeredWriters = new Dictionary<Out, Action<string, Color>>();

    public static readonly Output Instance = new Output();

    private void Output() { } // Empty private constructor so another instance cannot be created.

    public void Unregister(Out outType)
    {
        if (registeredWriters.ContainsKey(outType))
            registeredWriters.Remove(outType);
    }

    // Assumes caller will not combine the flags for outType here
    public void Register(Out outType, Action<string, Color> writer)
    {
        if (writer == null)
            throw new ArgumentNullException("writer");

        if (registeredWriters.ContainsKey(outType))
        {
            // You could throw an exception, such as InvalidOperationException if you don't want to 
            // allow a different writer assigned once one has already been.
            registeredWriters[outType] = writer;
        }
        else
        {
            registeredWriters.Add(outType, writer);
        }
    }

    public void WriteLine(Color color, string str, Const.Out output = Const.Out.Debug & Const.Out.Main)
    {
        bool includeDebug = false;
        #if DEBUG
        includeDebug = true;
        #endif

        foreach (var outType in registeredWriters.Keys)
        {
            if (outType == Const.Out.Debug && !includeDebug)
                continue;

            if (bitmask(output, outType))
                registeredWriters[outType](str, color);
        }
    }
}

Then elsewhere in your program, such as in the form class, to register a writer, do:

Output.Instance.Register(Const.Out.Main, (s, c) => this.Box.WriteLine(s, c));

When your form is unloaded you can then do:

Output.Instance.Unregister(Const.Out.Main);

Then another way would be to not use a singleton. You could then have more than one Output instance for different purposes and then inject these into your other classes. For instance, change the constructor for your main form to accept an Output parameter and store this is an object variable for later use. The main form could then pass this on to a child form that also needs it.

Upvotes: 1

Tan
Tan

Reputation: 2178

MethodInfo methodname = typeof(object).GetMethod("MethodA");

Then just use a if statement to check if methodname is null or not.

Upvotes: 0

welegan
welegan

Reputation: 3043

If your objects that have data that need to be written behave like this:

A always writes to console and log B always writes to log C always writes to console

For all data, then your best bet would be to declare an interface and have each of them implement the interface method for output. Then, in your calling code, declare them not as their actual types but instead of type IOutput or whatever interface u call that has the method. Then have two helper methods, one for actually outputting to console and one for actually outputting to a log file. A would call both helpers, B and C their respective ones.

If, on the other hand, your objects will write to various logs at differing times:

A, B and C sometimes write to console and sometimes to log, depending on some property

Then I would recommend you create an event handler for when a class wants something to be written. Then, have the logic that discerns what writes to console and what writes to log in a listener class and attach the appropriate ones to that output event. That way, you can keep the logic about what is being written to where in classes that encapsulate just that functionality, while leaving the A, B and C classes free of dependencies that may come to bite you down the road. Consider having a monolithic method as you describe which uses a bitmask. As soon as the behavior of A, B or C's logging changes, or if you need to add a new output, you suddenly need to worry about one class or method affecting all of them at once. This makes it less maintainable, and also trickier to test for bugs.

Upvotes: 0

Related Questions