Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloading a method which accepts `object` as default parameter type

I need to be able to call a method and pass in an object of an unknown type but then have the correct overload called. I also need a default implementation that accepts object as its parameter type. What I'm seeing is that the default overload is the only one that ever gets used.

Here's the gist of what I'm trying to do:

class Formatter
{
  private object Value;

  public Formatter(object val){
    Value = val;
  }

  public override string ToString()
  {
    return Format(Value);
  }

  private string Format(object value)
  {
    return value.ToString();
  }

  private string Format(DateTime value)
  {
    return value.ToString("yyyyMMdd");
  }
}

Ok, so far so good. Now I want to be able to do this:

public static class FancyStringBuilder()
{
  public static string BuildTheString()
  {
    var stringFormatter = new Formatter("hello world");
    var dateFormatter = new Formatter(DateTime.Now);
    return String.Format("{0} {1}", stringFormatter, dateFormatter);
  }
}

The result of FancyStringBuilder.BuildTheString() is "hello world 2012-12-21 00:00:00.000", when I expected "hello world 20121221"

The problem is that the overload that accepts a DateTime is not being called, instead defaulting to the overload which accepts an object. How can I call the proper method without resorting to a messy switch statement?

like image 201
jacobsimeon Avatar asked Nov 04 '22 08:11

jacobsimeon


1 Answers

In Formatter.ToString(), the override Formatter.Format(object) is always called. This is because the overload resolution happens at compile-time, not run-time. At compile-time, the only thing known about Value is that it's an object.

If you really want to distinguish incoming types, you'll need to do so in Formatter's constructor. In this case, rather than hanging on to the object, you could just call ToString() immediately and only store the formatted result:

class Formatter
{
    string formattedValue;

    public Formatter(object value)
    {
        formattedValue = value.ToString();
    }

    public Formatter(DateTime value)
    {
        formattedValue = value.ToString("yyyyMMdd");
    }

    public string ToString()
    {
        return formattedValue;
    }
}

Note that this does assume that your object isn't changing between the time you create the Formatter object and the time Formatter.ToString() is called, or at the very least that it's okay to take a snapshot of the string representation at the time the Formatter is created.

This also assumes that you know the incoming types at compile-time. If you want a truly run-time-only solution, you'll have to use the "is" operator or a typeof() comparison.

If your goal is just to provide custom ToString() formatting based on the incoming type, I'd probably do it using a list that maps from types to format strings:

static class Formatter
{
    private static List<Tuple<Type, string>> Formats;

    static Formatter()
    {
        Formats = new List<Tuple<Type, string>>();

        // Add formats from most-specific to least-specific type.
        // The format string from the first type found that matches
        // the incoming object (see Format()) will be used.
        AddMapping(typeof(DateTime), "yyyyMMdd");
        // AddMapping(typeof(...), "...");
    }

    private static void AddMapping(Type type, string format)
    {
        Formats.Add(new Tuple<Type, string>(type, format));
    }

    public static string Format(object value)
    {
        foreach (var t in Formats)
        {
            // If we find a type that 'value' can be assigned to
            // (either the same type, a base type, or an interface),
            // consider it a match, and use the format string.
            if (t.Item1.IsAssignableFrom(value.GetType()))
            {
                return string.Format(t.Item2, value);
            }
        }

        // If we didn't find anything, use the default ToString()...
        return value.ToString();
    }
}

With that, calling code then looks like:

Console.WriteLine(
    "{0} {1}",
    Formatter.Format(DateTime.Now),
    Formatter.Format("banana"));
like image 62
JaredReisinger Avatar answered Nov 21 '22 05:11

JaredReisinger