C# LINQ 获取泛型列表 T 的 ID



我有一个这个函数,我想把它转换为泛型函数。 如何获取泛型类的 Id(或其他属性)?

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
}
return items.FirstOrDefault(x => x.Id == id);

问题是:你需要告诉编译器,items序列的每个x都有一个属性Id

有两种方法:使用接口或使用属性选择器函数。后者在 LINQ 中广泛使用,例如在 GroupBy 中。

interface IId
{
Guid Id {get; }
}

要使用的所有T类都应实现此接口GetById

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

您的 GetById 似乎属于泛型类:

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

此方法的缺点:每个类都应实现此接口,并且它仅适用于 Guid ID。如果您有一个带有 int ID 的类,则不能再使用该方法。

为了克服这个问题,LINQ 使用了一个额外的参数,该参数说:"嘿,您应该将此属性用作键/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);
}
}

用法:

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

优势:

  • 这适用于所有类型的 ID:Guid、int、long、...
  • 你已隐藏这些项位于 JSON 文件中。如果您决定将它们保存在 XML 或数据库中,则更改将很少

可能的改进:

  • 仅读取文件内容一次:记住转换后的项目。

您必须有一个BaseClassInterface,然后:

BaseClass

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

和你的通用方法:

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); 
}

假设您有以下 json:

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

以及以下模型类,它表示 JSON 数组中的单个元素:

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

为了能够通过泛型类型引用其Id字段,您需要定义一个约束.
一种方法是通过接口,如下所示:

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

如果您的Data类实现了Identifiable接口,则可以在GetById方法中使用它:

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);
}

为了能够测试它,这里有一个简单的控制台应用程序:

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);
}

创建一个接口来"表示"Id属性.
Interface 可以比基类更灵活,因为可以有多个接口。因此,您可以拥有多个专用接口,并且可能为不同类型的接口提供不同的基类。

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);
}

如何在没有接口和基类的情况下做到这一点:

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);

最新更新