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

C#如何不通过继承按对象形状对泛型函数参数做类型检查?

方案1:使用partial类加公共接口(最优,完全保留编译时类型检查)

你的EF模型类已经标记为partial,不需要修改原有自动生成的类代码,只需要新增单独的代码文件,给需要的实体绑定公共接口即可:
首先定义公共属性接口:

public interface IAuditableEntity
{
    DateTime CreatedTime { get; set; }
    string CreatorName { get; set; }
}

然后在单独的代码文件中为实体类补充partial定义,实现接口:

// 不需要写任何额外代码,原有类已经有对应属性,自动匹配接口实现
public partial class BlogPost : IAuditableEntity {}
public partial class Comment : IAuditableEntity {}

之后你的泛型方法就可以直接加接口约束:

public void SetInitialProperties<T>(T dbEntity, DateTime createdTime, string creatorName) 
    where T : IAuditableEntity
{
    dbEntity.CreatedTime = createdTime;
    dbEntity.CreatorName = creatorName;
}

调用方式和你期望的完全一致,所有属性赋值都会在编译期做类型校验,不存在运行时风险。

方案2:EF Core 全局拦截自动赋值(免手动调用)

如果你用的是EF Core,可以直接重写DbContext的SaveChanges/SaveChangesAsync方法,在数据提交前自动给新增实体赋值,连手动调用SetInitialProperties的步骤都可以省略:

public override int SaveChanges()
{
    var addedEntities = ChangeTracker.Entries()
        .Where(e => e.State == EntityState.Added)
        .Select(e => e.Entity);
    
    foreach (var entity in addedEntities)
    {
        // 检查实体是否有对应属性,有则自动赋值
        var createdTimeProp = entity.GetType().GetProperty("CreatedTime");
        if (createdTimeProp != null && createdTimeProp.PropertyType == typeof(DateTime))
        {
            createdTimeProp.SetValue(entity, DateTime.UtcNow);
        }
        var creatorNameProp = entity.GetType().GetProperty("CreatorName");
        if (creatorNameProp != null && creatorNameProp.PropertyType == typeof(string))
        {
            creatorNameProp.SetValue(entity, 你的当前操作人变量);
        }
    }
    return base.SaveChanges();
}

如果担心反射性能,可以提前缓存所有实体类型的属性访问委托,开销可以忽略。

方案3:无接口场景下的类型安全实现

如果确实无法给实体类加接口,可以用表达式树编译出强类型的赋值委托,既不需要dynamic,也比普通反射性能好很多:

public static class EntityInitializer
{
    public static void SetInitialProperties<T>(T entity, DateTime createdTime, string creatorName)
    {
        EntityAccessor<T>.SetCreatedTime(entity, createdTime);
        EntityAccessor<T>.SetCreatorName(entity, creatorName);
    }

    private static class EntityAccessor<T>
    {
        public static readonly Action<T, DateTime> SetCreatedTime = BuildPropertySetter<DateTime>(nameof(CreatedTime));
        public static readonly Action<T, string> SetCreatorName = BuildPropertySetter<string>(nameof(CreatorName));

        private static Action<T, TValue> BuildPropertySetter<TValue>(string propertyName)
        {
            var entityParam = Expression.Parameter(typeof(T), "entity");
            var valueParam = Expression.Parameter(typeof(TValue), "value");
            var propertyAccess = Expression.Property(entityParam, propertyName);
            var assignExpr = Expression.Assign(propertyAccess, valueParam);
            return Expression.Lambda<Action<T, TValue>>(assignExpr, entityParam, valueParam).Compile();
        }
    }
}

调用方式和之前一致,如果T没有对应属性,会在第一次调用时抛出明确的异常,比dynamic的隐式报错更安全,性能也接近原生赋值。

内容的提问来源于stack exchange,提问作者Benjamin U.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 21:45:03