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 th开发者_开发知识库e 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().
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.
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.
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.
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
.
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.
精彩评论