You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#如何创建兼容多类共有PrimaryIdentifier属性的泛型方法

你当前写法报错的核心原因是Cast<T>()执行的是强类型转换,当传入集合的实际元素类型和转换目标不一致时,必然抛出类型转换异常。要实现符合DRY原则的泛型兼容逻辑,优先选类型安全的实现方案,完全不需要做冗余的两次类型转换判断。

方案1:接口约束(推荐,零性能损耗+编译期类型检查)

这是最符合C#编码规范的实现方式,利用泛型约束直接在编译期保证传入类型包含目标属性,没有任何运行时转换开销:

  1. 先抽取两个DTO的公共属性契约,定义专用接口
    /// <summary>
    /// 标记所有包含PrimaryIdentifier属性的实体类型
    /// </summary>
    public interface IHasPrimaryIdentifier
    {
        string PrimaryIdentifier { get; set; }
    }
    
  2. 让两个DTO类实现该接口,原有业务属性不需要做任何修改
    public class classOneDTO : IHasPrimaryIdentifier
    {
        public int PatientID { get; set; }
        public string PrimaryIdentifier { get; set; }
        public string FirstName{ get; set; }
        // 其余原有属性保持不变
    }
    
    public class classTwoDTO : IHasPrimaryIdentifier
    {
        public int EntryID { get; set; }
        public string PrimaryIdentifier { get; set; }
        public string Location{ get; set; }
        // 其余原有属性保持不变
    }
    
  3. 给泛型方法添加类型约束,直接通过接口访问属性即可,不需要任何Cast转换
    private void GenericMethod<T>(List<T> workList, GridView grid, int columnNo) where T : IHasPrimaryIdentifier
    {
        if (!workList.Any()) return;
        
        string codeString = default;
        // 直接查询符合条件的元素,两种DTO都能正常处理
        var matchItem = workList.FirstOrDefault(x => !string.IsNullOrEmpty(x.PrimaryIdentifier));
        if (matchItem != null)
        {
            codeString = matchItem.PrimaryIdentifier;
        }
    
        // 后续GridView绑定逻辑保持原有实现即可
    }
    

这个方案的优势:

  • 编译期即可校验类型合法性,如果传入的类型没有PrimaryIdentifier属性会直接编译报错,不会触发运行时异常
  • 没有类型转换、反射的额外性能开销
  • 后续新增其他带相同属性的DTO时,只要实现该接口就能直接复用方法,不需要修改方法逻辑,完全符合开闭原则
方案2:无侵入兼容(无法修改DTO定义时使用)

如果两个DTO是自动生成的代码、或者你没有权限修改类定义加接口,可以用下面两种兼容写法,不需要改动原有DTO:

  • 临时兼容写法(适合当前仅需支持两种DTO的场景):用OfType<T>()代替Cast<T>(),该方法会自动过滤掉类型不匹配的元素,不会抛转换异常

    private void GenericMethod<T>(List<T> workList, GridView grid, int columnNo)
    {
        if (!workList.Any()) return;
        
        string codeString = default;
        object matchItem = workList.OfType<classOneDTO>().FirstOrDefault(x => !string.IsNullOrEmpty(x.PrimaryIdentifier));
        matchItem ??= workList.OfType<classTwoDTO>().FirstOrDefault(x => !string.IsNullOrEmpty(x.PrimaryIdentifier));
        
        if (matchItem != null)
        {
            codeString = matchItem is classOneDTO one 
                ? one.PrimaryIdentifier 
                : ((classTwoDTO)matchItem).PrimaryIdentifier;
        }
    }
    

    注意:这种写法后续新增DTO类型时必须手动修改方法添加判断逻辑,可维护性差,仅适合临时快速修复。

  • 通用泛型写法(需要支持任意带PrimaryIdentifier属性的类型):用ConcurrentDictionary缓存属性访问委托,避免重复反射的性能开销

    // 全局缓存不同类型的PrimaryIdentifier属性读取委托
    private static readonly System.Collections.Concurrent.ConcurrentDictionary<Type, Func<T, string>> _idGetterCache = new();
    
    private void GenericMethod<T>(List<T> workList, GridView grid, int columnNo)
    {
        if (!workList.Any()) return;
        
        string codeString = default;
        var entityType = typeof(T);
        // 从缓存拿属性读取委托,不存在则动态创建
        var getId = _idGetterCache.GetOrAdd(entityType, t =>
        {
            var prop = t.GetProperty("PrimaryIdentifier", typeof(string));
            if (prop == null) throw new ArgumentException($"类型{t.Name}未定义string类型的PrimaryIdentifier属性");
            return (Func<T, string>)Delegate.CreateDelegate(typeof(Func<T, string>), prop.GetMethod);
        });
    
        var matchItem = workList.FirstOrDefault(x => !string.IsNullOrEmpty(getId(x)));
        if (matchItem != null)
        {
            codeString = getId(matchItem);
        }
    }
    

    该写法的缺点是没有编译期类型校验,如果传入的类型不带对应属性会抛出运行时异常,性能也略低于接口约束方案,仅作为无法修改DTO定义时的备选。

内容的提问来源于stack exchange,提问作者user2050079

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 20:09:11