0

I have an abstract GetAsync call in a web API to get all types of classes. (actual services with specific code required by a class would derive from this abstract definition) Lets say I'm using Injected MemoryCache to temporaritly replace the DB to test this:

public async Task<IEnumerable<T>> GetAsync()
{
    if (!memoryCache.TryGetValue(typeof(T).Name, out List<T>? dtoStuff))
    {
        dtoStuff = new List<T>();
    }
    return dtoStuff;
}

This works great to return a list of any such object.

All type 'T' objects are based off of the same Interface

public interface ITheObjectInterface
{
}

public class TheObject : ITheObjectInterface
{
   ...
}

Now, I want to get a specific item by 'id', and I know ALL types of 'T' have a member that is defined as :

public string? Id { get; set; }

But that's not in the existing ITheObjectInterface that the classes are based off of.

My abtract call is :

public async Task<T> GetAsync(string id)
{
      // but how do I access the 'Id' member of any List of type 'T'?

      if (!memoryCache.TryGetValue(typeof(T).Name, out List<T>? dtoStuff))
      {
          dtoStuff = new List<T>();
      }

      return dtoStuff.FirstOrDefault(o => o.Id== id);
      //                                    ^^^^^^^^^^^^^^^^^ error

This has probably been answer 'n' times, but I can't find any answer... so just push me in the right direction

Robert Achmann
  • 1,986
  • 3
  • 40
  • 66
  • Add an `Id` property to your Interface? – Ryan Wilson Mar 16 '23 at 19:07
  • haha - yes, that was my first 'want'... but I was rejected. – Robert Achmann Mar 16 '23 at 19:14
  • 1
    If you can't put it in an interface then your choices are to use [reflection](https://stackoverflow.com/a/4939524/2791540) or [dynamic](https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/interop/using-type-dynamic). – John Wu Mar 16 '23 at 19:25
  • 3
    @RobertAchmann Who rejected it? Your post makes it sound like you have the ability to make the design decisions. I would ask whomever rejected it to read this: [performance-dynamic-vs-reflection](https://stackoverflow.com/questions/47692428/performance-dynamic-vs-reflection). no matter if you use reflection or dynamic, you are going to add overhead that wouldn't be there if you had the property in the interface or created another interface like **MakePeaceGreatAgain** suggested. – Ryan Wilson Mar 16 '23 at 19:34
  • 1
    Alternativly to changing the existing interface just create a new one and make your classes implement that. Honestly: who puts that kind of assignment and rejects to modify the interface? – MakePeaceGreatAgain Mar 16 '23 at 19:35
  • When you say "abstract" do you actually mean "generic"? Because I don't see how this is `abstract`. And to add to MPGA: who doesn't include an ID in such an interface in the first place? – Fildor Mar 16 '23 at 19:48
  • I think I'll push harder for the interface change. I agree. – Robert Achmann Mar 17 '23 at 11:33
  • @Fildor these are abstract as they just define some common functionality of a group of entities. But they do the same thing on all types found that based on the same interface... So which is it? – Robert Achmann Mar 17 '23 at 12:54
  • Well `abstract` has a very specific meaning in C#. What we are seeing in the code is generic. But the type arg does not seem to be constrained to the interface. – Fildor Mar 17 '23 at 13:05
  • @Fildor But I could never have a controller as public async Task GetAsync(string id). You'd need to create a class based on this for the actual type. There is generic code, yes, but it is abstract. I can use it to develop each controller on the ITheObjectInterface – Robert Achmann Mar 17 '23 at 14:45
  • @RyanWilson I was able to push the interface change. Thanks – Robert Achmann Mar 17 '23 at 14:48

1 Answers1

1

If you can't add Id to the interface then I suppose you could use Reflection to get it and set it. Perhaps you could write a couple extension methods on ITheObjectInterface:

static class ExtensionMethods
{
    public static string GetId<T>(this T source) where T : ITheObjectInterface
    {
        return (string)source.GetType().GetProperty("Id")?.GetValue(source);
    }

    public static void SetId<T>(this T source, string newValue) where T : ITheObjectInterface
    {
        var property = source.GetType().GetProperty("Id");
        if (property == null) throw ArgumentException();
        property.SetValue(source, newValue);
    }
}

Then you can do something like this:

return dtoStuff.FirstOrDefault(o => o.GetId() == id);

    
John Wu
  • 50,556
  • 8
  • 44
  • 80