简体   繁体   中英

Approach question regarding Generics in C#

Say you have a group of objects you're creating to handle some XML parsing and all of them take the exact same object, XElement ... as such

public class User
{
    public User(XElement xmlElement)
    {
          Id = xmlElement.GetElementValue("UserId"); 
    }

    public string Id { get; set; }
}

What I would like to do is a method kinda like this ..

public static T ToParsedObject<T>(this XElement xmlElement) where T : new()
{
    return new T(xmlElement);
}

I don't think it's possible to do a static (extension method) like this, but I would like to make this a single method I can re-use. I'm tired of writing ones like ...

public static User ToUser(this XElement xmlElement)
{
    return new User(xmlElement);
}

Any ideas or guidance?

Unfortunately there's no way to provide a constructor call like this.

Two potential options:

  • Make the type mutable (ick) and make it implement an interface, eg IParseableFromXElement. Then you could write:

     public static T ToParsedObject<T>(this XElement xmlElement) where T : new(), IParseableFromXElement { T ret = new T(); ret.Parse(xmlElement); return ret; } 
  • Have a dictionary from type to delegate constructing the type:

     static Dictionary<Type, Func<XElement, object>> factories = new Dictionary<Type, Func<XElement, object>> { { typeof(User), x => new User(x) }, ... }; public static T ToParsedObject<T>(this XElement xmlElement) { Func<XElement, object> factory = factories[typeof(T)]; return (T) factory(xmlElement); } 

Both are somewhat hacky, but they'll work...

You can pull this off with reflection:

public static class XElementExtensions
{
    public static T To<T>(this XElement el)
    {
        //var ctor = typeof(T).GetConstructor(new[] { typeof(XElement) });
        //if (ctor == null) /* do something */

        return (T)Activator.CreateInstance(typeof(T), new[] { el });
    }
}

You don't need the constructor check in there, but it would be necessary if you wanted to take special action, like returning default(T) .

You would use this method like this:

User u = xmlElement.To<User>();

I do wonder, though, what the benefit of this is over simply calling the constructor on your objects:

User u = new User(xmlElement);

Heck, it's one less character! :)

How about reflection?

public static T ToParsedObject<T>(this XElement xmlElement) 
    where T : new()
{
    return (T) Activator.CreateInstance(typeof(T), xmlElement);
}

As Matt pointed out, this is not really saving you any typing. You still need to write the conversion code in the classes. But perhaps there's some misunderstanding about your question. Isn't it that you actually want to remove the constructor and create a single method extension to XElement that takes the same syntax each time again? Something like this?

/// <summary>
/// Try to parse an XElement into a matching type. Type must have an Id
/// </summary>
/// <returns>newly created type with Id and other properties filled</returns>
public static T ToParsedObject<T>(this XElement xmlElement)
    where T : new()
{
    T item = new T();
    Type type = typeof(T);

    // you can use GetProperties to go through all of them dynamically
    PropertyInfo prop = type.GetProperty("Id");
    prop.SetValue(item, xmlElement.Element(
        XName.Get(type.Name + "Id")),         // creates UserId or CustomerId etc
        null);
    return item;
}

Depending on how well you can rely on the correct naming of the XML elements, you can use this approach to take away the burden of creating individual type conversion code. You can even go through each property and find a matching XML element name and value for it, dynamically. Write once, use everywhere!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM