5

I'm writing a tool that interfaces with an API for another piece of software. Part of my tool will need to generate reports about the various objects found through the API, and I want these reports to contain simple strings that identify each object. By default I plan to use ToString() to generate the string for each object. However, not surprisingly I've found that the default ToString() implementations in this API aren't to descriptive.

Initially I was thinking of doing something like the code below with a long Switch statement. Although this would most likely become unmanageably long.

public string GetAPIObjectDescrition(object obj)
{
     Type t = obj.GetType();

     Switch(t)
     { 
         Case typeof(SomeAPIType):
             SomeAPIType x = (SomeAPIType)obj;
             return  x.SomeProperty;             


         Case typeof(SomeOtherAPIType):
             SomeOtherAPITypex = (SomeOtherAPIType)obj;
             return  x.SomeOtherProperty;

         default:
             return x.ToString();
     }
} 

Next I tried using extension methods (see the code below). CustomObjectDescription() worked as expected, but when I tried to call ToString() it just returns the default ToString() results. I've never used extension methods before so I could be completely off base thinking something like this is even possible.

I can't guarantee that there will be a CustomObjectDescription() extension for every Type encountered in the API, so if I take this route I would end up having to use reflection each time to check if the current object has a GetObjectDescription() extension. I'd like to avoid using reflection if at all possible.

public static class APIObjectDescriptionExtensions
{
    public static string ToString(this APIObject element)
    {
        return "ElementName = " + element.Name + " ElementID =" + element.Id.IntegerValue.ToString();
    }

    public static string CustomObjectDescription(this APIObject element)
    {
        return "ElementName = " + element.Name + " ElementID =" + element.Id.IntegerValue.ToString();
    }
}

Does anyone have any other suggestions on how I should approach this problem? I'd prefer a solution where the code for each API Type is independent from one another (no giant Switch statement).

Also if possible I'd like the description string code for one type to inherit to sub types unless those types have their own unique description string code.

I think there might be a better solution that involves creating custom TypeConverters or maybe overriding/extending System.Convert.ToString()?


Update

I think the example below might help clarify what I'm trying to do. Ultimately I want to be able to take any arbitrary class from this API, the Type of which is not known until run time, and generate a description string. If the Type has my custom extension method then it should be used, otherwise the code should fall back on plain old ToString().

    public static string GetDataDescription(object o)
    {
        //get the type of the input object
        Type objectType = o.GetType();

        //check to see if a description extension method is defined
        System.Reflection.MethodInfo extensionMethod = objectType.GetMethod("MyDescriptionExtensionMethod");

        if (extensionMethod != null)
        {
            //if a description extension method was found returt the result
            return (string)extensionMethod.Invoke(o, new object[] { });
        }
        else
        {
            //otherwise just use ToString();
            return o.ToString();
        }
    }

This code above doesn't work though because extension methods aren't found by GetMethod().

Eric Anastas
  • 21,675
  • 38
  • 142
  • 236
  • You can't hide an instance method using extension methods because instance methods are checked for first when the compiler tries to bind a method invocation expression; see the specification (§7.6.5.1) for the details. – jason Nov 18 '10 at 02:25
  • an instance method will always be used before any extension method with the same name (all else being equal). So using ToString that way is not possible. – BrokenGlass Nov 18 '10 at 02:26
  • Possible duplicate of http://stackoverflow.com/questions/4093501/how-do-i-override-tostring-and-implement-generic?rq=1 – Michael Freidgeim May 04 '13 at 21:17

5 Answers5

2

You could provide a wrapper for each of the classes similar to this:

    public class SomeAPITypeWrapper : SomeAPIType
    {
        public override string ToString()
        {
            return SomeProperty;
        }
    }

    public class SomeOtherAPITypeWrapper : SomeOtherAPIType
    {
        public override string ToString()
        {
            return SomeOtherProperty;
        }
    }

This certainly allows for using base classes/sub classes as requested in your question. It also keeps it clean and within your object model itself instead of in a switch statement or helper class.

Chris Conway
  • 16,269
  • 23
  • 96
  • 113
  • 1
    First I wouldn't be surprised if the API Classes are sealed. If not then how do I get an instance of SomeAPItypeWrapper? The API is going to return a SomeAPIType instance. So I'll still need some consistent way to map between the true API type and the custom wrapper class Type to use. – Eric Anastas Nov 18 '10 at 04:30
  • Yeah, but you can use composition instead of inheritance then. – Ed S. Nov 18 '10 at 04:59
  • 1
    This is 'introduce local extension' - caveats include: you need to control creation (create derived type) whenever an instance of the base type is being created. Second: the base types need to be non-sealed. – Gishu Nov 18 '10 at 08:54
0

You could defer all tostringing to a separate concern of your application. StatePrinter (https://github.com/kbilsted/StatePrinter) is one such API where you can use the defaults or configure depending on types to print.

var car = new Car(new SteeringWheel(new FoamGrip("Plastic")));
car.Brand = "Toyota";

then print it

StatePrinter printer = new StatePrinter();
Console.WriteLine(printer.PrintObject(car));

and you get the following output

new Car() {
    StereoAmplifiers = null
    steeringWheel = new SteeringWheel()
    {
        Size = 3
        Grip = new FoamGrip()
        {
            Material = ""Plastic""
        }
        Weight = 525
    }
    Brand = ""Toyota"" }

and with the IValueConverter abstraction you can define how types are printer, and with the FieldHarvester you can define which fields are to be included in the string.

Carlo V. Dango
  • 13,322
  • 16
  • 71
  • 114
0

Did you try using another name other then ToString() in your extension class? I am not completely sure about extension methods either, but I am guessing the base.ToString was getting called instead of yours. Possibly making a ToDescription() extension method would yield better results.

Jason
  • 1,879
  • 16
  • 19
  • Yes CustomObjectDescription() as I mentioned in the OC, and yes it worked. The problem with that is there is no way to know there is a CustomObjectDescription() extension method for every API Type. – Eric Anastas Nov 18 '10 at 04:26
0

If a given method call can be resolved by an instance method and an extension method, the instance method is given preference. So extension methods need to be named such that they don't have same names as methods in the extended type.

From the code above, it seems that you don't control the source of APIObject and its derivations. So your options are 'Introduce Foreign Method' and 'Introduce Local Extension'

  • I'd try foreign method (which is similar to C# extension methods).. not sure why you would need reflection though. If the extension method doesn't exist, it'd be a compile-time error. How are you consuming this method ?
  • Finally switch statements are not that bad... unless they are very long/need frequent changes/duplicated across locations.
Gishu
  • 134,492
  • 47
  • 225
  • 308
  • There is a third option: use Cecil to introduce code into their library. – Joshua Nov 18 '10 at 02:31
  • @Joshua - wow. Not sure how this works... but doesn't this equal assembly tampering. Does it work with strongly-typed assemblies and on other platforms than Mono/Linux? – Gishu Nov 18 '10 at 02:39
  • I have a powerful tool in my toolchest for overriding strong naming even in Microsoft's .NET so long as there are no mixed-mode assemblies up the tree from the you to the one being overridden. Mono.Cecil and Mono.Security can be compiled and used against Microsoft's .NET Framework. – Joshua Nov 18 '10 at 03:52
0

I suggest making a Dictionary<Type,Converter<object,string>>. Then you can look for a custom stringizer, and if none is found, call ToString.

Note, the dictionary will check for an exact match on types, so if you want to handle subclasses you'll have to write some additional code to see whether base types are listed in the dictionary (hint: if you match a base class, or even if you don't, go ahead and add the actual derived type to the dictionary so you won't have to recurse through the inheritance tree again).

Note that you can build an "open delegate" for Object.ToString() which conforms to the Converter<object,string> contract and use that as a default, even store it in the dictionary, instead of special-casing the call to ToString.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720