-2

I have a this function that I want to convert to a generic function. how to get the Id (or other property) of a generic class?

public T GetById(Guid id)
{
    string jsonFile = @"D:\StorageFile.txt";
    var json = File.ReadAllText(jsonFile);
    var items = JsonConvert.DeserializeObject<List<T>>(json);

    return items.FirstOrDefault(x => x.Id == id); // error is here in Id
}
user10860402
  • 912
  • 1
  • 10
  • 34
  • 2
    You'll have to instruct the compiler that `T` is indeed having that `Id` property by adding a *generic constraint*: `public T GetById(Guid id) where T : BaseClassOrInterfaceDefiningThatIdProperty` – haim770 Jan 05 '21 at 07:07
  • 1
    Why not use a base class or interface as the return type instead? – Caius Jard Jan 05 '21 at 07:08

5 Answers5

1

You must have a BaseClass or Interface and then :

BaseClass :

public class BaseClass
{
   public Guid Id { get; set; }
}

And your generic method :

public static T GetById<T>(Guid id) where T : BaseClass
{
   string jsonFile = @"D:\StorageFile.txt";
   var json = File.ReadAllText(jsonFile);
   var items = JsonConvert.DeserializeObject<List<T>>(json);
   return items.FirstOrDefault(x => x.Id == id); 
}
AmirNorouzpour
  • 1,119
  • 1
  • 11
  • 26
1
return items.FirstOrDefault(x => x.Id == id);

The problem is: you need to tell the compiler that every x of the sequence of items has a property Id.

There are two methods for this: use interfaces, or use a propertySelector function. The latter is widely used in LINQ, for instance in GroupBy.

interface IId
{
    Guid Id {get; }
}

All your T classes where you want to use GetById should implement this interface:

class Customer : IId
{
    public Guid Id {get; set;}
    ...
}
class Order : IId {...}
class Product : IId {...}

Your GetById seems to be in a generic class:

class ItemCollection<T> Where T : IId
{
    T GetById(Guid Id)
    {
        ...
        return items.FirstOrDefault(x => x.Id == id);
    }
}

Disadvantages of this method: Every class should implement this interface, and it works only with Guid Ids. If you have one class with an int Id, then you can't use the method anymore.

To overcome this problem, LINQ uses an extra parameter that says: "Hey, you should use this property as Key / Id / ..."

class ItemCollection<Tsource, Tid>
{
    // Tsource: the type of items in this collection; your original T
    // Tid: the type of the Id of the items; in your case: Guid

    public ItemCollection(string fileName, Func<Tsource,Tid> idSelector)
    {
         this.fileName = fileName;
         this.idSelector = idSelector;
    }

    readonly string fileName;
    readonly Func<Tsource,Tid> IdSelector

    public string ReadFile()
    {
        return File.ReadAllText(this.fileName);
    }

    public List<T> ReadItems()
    {
        return JsonConvert.DeserializeObject<List<T>>(this.ReadFile);
    }

    public Tsource GetById(Tid id)
    {
         return this.ReadItems()
             .FirstOrDefault(item => this.IdSelector(item.Id) == id);
    }
}

Usage:

const string fileName = @"D:\StorageFile.txt";
var customers = new ItemCollection<Customer, Guid>(
    fileName,
    customer => customer.Id)

Guid customerId = ...
Customer customer = customers.GetById(customerId);

Advantages:

  • This works with all types of Ids: Guid, int, long, ...
  • You've hidden that the items are in a Json file. If you decide to save them in XML or in a database, changes will be minimal

Possible improvement:

  • Read the file contents only once: remember the converted items.
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
0

Let's suppose you have the following json:

[
  {
    "Id": "00ead552-28c1-4bd9-929c-bf8c0ed45178",
    "Description": "A"
  },
  {
    "Id": "9a734e69-7116-4164-a4da-dc9615551628",
    "Description": "B"
  }
]

and the following model class, which represents a single element in the json array:

public class Data
{
    public Guid Id { get; set; }
    public string Description { get; set; }
}

In order to be able to refer to its Id field via a generic type you need to define a constraint.
One way to do this is via an interface, like this:

public interface Identifiable
{
    Guid Id { get; set; }
}

If your Data class implements the Identifiable interface then you are ready to use that in your GetById method:

public static T GetById<T>(string path, Guid id) where T : Identifiable
{
    var json = File.ReadAllText(path);
    var items = JsonConvert.DeserializeObject<List<T>>(json);
    return items.FirstOrDefault(x => x.Id == id);
}

In order to be able to test it here is a simple console application:

static void Main(string[] args)
{
    const string filePath = "sample.json";
    var dataSource = new List<Data>
    {
        new Data {Id = Guid.NewGuid(), Description = "A"},
        new Data {Id = Guid.NewGuid(), Description = "B"},
    };
    var json = JsonConvert.SerializeObject(dataSource);
    File.WriteAllText(filePath, json);

    var data = GetById<Data>(filePath,dataSource[0].Id);
    Console.WriteLine(data.Description);
}
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
0

Create an interface to "represent" Id property.
Interface can be more flexible than base class, because you can have multiple interfaces. So you can have multiple dedicated interfaces and possible have different base classes for different types.

public interface IIdentifier
{
    public Guid Id { get; }
}

public T GetById<T>(Guid id) where T : IIdenntifier
{
    var items = ReadFromFile<T>("filePath");

    return items.FirstOrDefault(item => item.Id == id);
}
Fabio
  • 31,528
  • 4
  • 33
  • 72
0

How to do that without interfaces and base classes:

public T GetByPredicate<T>(Func<T, bool> predicate)
{
    string jsonFile = @"D:\StorageFile.txt";
    var json = File.ReadAllText(jsonFile);
    var items = JsonConvert.DeserializeObject<List<T>>(json);

    return items.FirstOrDefault(predicate); 
}
var some = GetByPredicate<Some>(s => s.Id == guid);
Svyatoslav Danyliv
  • 21,911
  • 3
  • 16
  • 32