3

This question is related to this question: Given System.Type T, Deserialize List<T>

Given this function to retrieve a list of all elements...

 public static List<T> GetAllItems<T>()
 {
    XmlSerializer deSerializer = new XmlSerializer(typeof(List<T>));
    TextReader tr = new StreamReader(GetPathBasedOnType(typeof(T)));
    List<T> items = (List<T>)deSerializer.Deserialize(tr);
    tr.Close();
 }

...I want to create a function to retrieve just one item of those with the needed UID (Unique ID):

public static System.Object GetItemByID(System.Type T, int UID)
{

    IList mainList = GetAllItems<typeof(T)>();
    System.Object item = null;

    if (T == typeof(Article))
        item = ((List<Article>)mainList).Find(
            delegate(Article vr) { return vr.UID == UID; });
    else if (T == typeof(User))
        item = ((List<User>)mainList).Find(
            delegate(User ur) { return ur.UID == UID; });

    return item;
}

However, this does not work since GetAllItems<typeof(T)>(); call is not properly formed.

Question 1a: How can I fix the second function to properly return a unique element, given that all classes that will call GetItemByID() have UID as an element in them? I'd love to be able to do public static <T> GetItemByID<T>(int UID) if possible.

Question 1b: Same question, but suppose I cannot modify the function prototype of GetItemByID?

Community
  • 1
  • 1
Carl J.
  • 71
  • 3
  • 4

7 Answers7

3

1a. Ensure that all T implement interface IUniqueIdentity which defines a property UID then constrain a generic method to accept only IUniqueIdentity types. So:

public static T GetItemById<T>(int UID) where T:IUniqueIdentity
{
    IList<T> mainList = GetAllItems<T>();

    //assuming there is 1 or 0 occurrences, otherwise FirstOrDefault
    return mainList.SingleOrDefault(item=>item.UID==UID); 

}

public interface IUniqueIdentity
{
    int UID{get;}
}
spender
  • 117,338
  • 33
  • 229
  • 351
  • Interesting - looks much cleaner. When I try to compile this, though, I get an error `"Error 12 Cannot convert lambda expression to delegate type 'System.Func' because some of the return types in the block are not implicitly convertible to the delegate return type"` – Carl J. Nov 08 '10 at 22:56
  • I used assignment instead of equality in the lambda. Fixed. – spender Nov 09 '10 at 01:04
1

How can I fix the second function to properly return a unique element, given that all classes that will call GetItemByID() have UID as an element in them?

Modify the GetItemByID method to itself be generic: public static T GetItemByID<T>(int UID)

Same question, but suppose I cannot modify the function prototype of GetItemByID

GetAllItems<typeof(T)>() does not work, as you've noted, since calling generic methods requires knowledge of the type T at compile time. Instead, use MethodInfo.MakeGenericMethod() to create a closed form reference to the method for a particular Type of T.

public static System.Object GetItemByID(System.Type type, int UID) 
{
    Type ex = typeof(YourClass);
    MethodInfo mi = ex.GetMethod("GetItemByID");

    MethodInfo miConstructed = mi.MakeGenericMethod(type);

    // Invoke the method.
    object[] args = new[] {UID};
    return miConstructed.Invoke(null, args);
}
LBushkin
  • 129,300
  • 32
  • 216
  • 265
0

The way I'm doing it is by using a lambda. Maybe I'm over-simplifying what you actually want to accomplish, but here's how I'm getting an object out of a list by its Id in an MVC5 app:

...
List<TodoModel> todos = new List<TodoModel>() {
    new TodoModel() {
        id = 0,
        name = "Laundry!",
        desc = "Whites and darks."
    },
    new TodoModel() {
        id = 1,
        name = "Dishes",
        desc = "Oh, first buy dishes."
    },
    new TodoModel() {
        id = 2,
        name = "Twitter Bootstrap",
        desc = "Check out Grids."
    }
};

ViewBag.Item = todos.Find(o => ((TodoModel)o).id == id);

return View();
...

Simplified example, of course, but thought this may come in handy for the Questioner or others.

Cody
  • 9,785
  • 4
  • 61
  • 46
0

In response to your 1A, the usual solution is to extract the UID property into an

interface IHasUID { public int UID { get; } }

which both Article, User, and whatever else implement; and then add a constraint

public static T GetItemByID<T>(int UID) where T : IHasUID

If you don't have control over the types Article, User, et al, and you can't make them implement this interface, then you're basically ** and you won't be able to do this in a typesafe way.

p.campbell
  • 98,673
  • 67
  • 256
  • 322
mqp
  • 70,359
  • 14
  • 95
  • 123
0

Here's something in Reflection if you don't mind its cost:

public static System.Object GetItemByID(System.Type T, int UID)
{

    IList mainList = GetAllItems().OfType<typeof(T)>();

    return mainList.Find(i => (int)(i.GetType().GetFields().Where(f => f.Name == "UID").FirstOrDefault().GetValue(i)) == UID);

}

It basically uses Reflection to retrieve the value of a field called UID.

Use this only if you don't have the ability to change Article or User, since those should be inheriting from one interface to allow access to a UID field without having to reflect them.

BeemerGuy
  • 8,139
  • 2
  • 35
  • 46
0

Not sure if it fits into your system as is, but have you thought about implementing a KeyedCollection instead of just a generic list?

http://msdn.microsoft.com/en-us/library/ms132438%28VS.95%29.aspx

Or there might be some insight into a custom solution from the base class code.

ulty4life
  • 2,972
  • 1
  • 25
  • 31
-1

System.Type is already a type, no need to do typeof.

then I would do: if (T == typeof(Article)) item = ((List)mainList).Find(vr => vr.UID == UID);

MrFox
  • 4,852
  • 7
  • 45
  • 81