【发布时间】:2022-01-23 05:33:01
【问题描述】:
我对 C# 中的匿名对象有疑问。情况如下:
我有一个 C# Web 应用程序,它不使用传统的 ASP.NET Razor 引擎,而是使用 RazorEngine 开源项目 (https://github.com/Antaris/RazorEngine)。我不确定这是否相关,但可能是。
我将模型对象传递给我正在显示的每个页面。每个模型对象都是不同的,有很多页面,因此有很多不同的模型对象,但我宁愿不必为每个模型声明单独的类,这就是我一直使用匿名类的原因:
// In method which displays page A:
var model = new {
Lang = _lang,
PropX = "foo",
PropY = "bar"
};
RazorEngine.Run("templateA", model);
// In a different method which displays page B:
var model = new {
Lang = _lang,
PropZ = "smu"
};
RazorEngine.Run("templateB", model);
您可能会注意到,botn(实际上是所有)这些模型都有一个共同的属性(“Lang”属性),实际上有一些共同的属性(Lang 是上面示例中为简化问题而显示的唯一一个)。
我的主要问题是我试图确保将这些属性添加到所有模型中,以保证它们包含在所有页面中,如果我后来决定添加一个新的公共属性,那么我可以在一个地方完成。
当然,一种方法是删除匿名类,并使用全部继承自一个基类的类型化类,该基类将声明公共属性。但这将是很多样板代码,如果有其他解决方案,我会更喜欢。
另一种解决方案是在模型对象的子属性中声明公共属性,或者在子对象中声明各个页面属性:
// Either like this:
var model = new {
Common = GetCommonModelProperties(),
PropX = "foo",
PropY = "bar"
};
public object GetCommonModelProperties()
{
return new {
Lang = _lang
};
}
// etc.
// or like this:
var pageModel = new {
PropX = "foo",
PropY = "bar
};
var model = CreateModel(pageModel);
RazorEngine.Run("templateA", model);
// where CreateModel could be implemented like this:
public object CreateModel(object pageModel)
{
return new
{
Lang = _lang,
// etc., whatever common properties there exist
Data = pageModel
};
}
这种方法的问题是我必须修改我的所有模板,或者这些页面引用公共属性的所有实例(我必须将所有 Model.Lang 实例重命名为 Model.Common.Lang),或者到单个页面数据(将 Model.AnyProperty 修改为 Model.Data.AnyProperty)。当然,当这种重写发生时,出错的风险很大。
那么:有没有办法创建一个匿名对象,它的许多属性始终相同,但其余的可以动态指定?
我尝试创建两个单独的对象,然后将它们合并为一个,使用以下问题中的代码:Merging anonymous types
var commonModel = new {
Lang = _lang
};
var pageModel = new {
PropX = "foo",
PropY = "bar"
};
var model = Merge(commonModel, pageModel);
是的,这行得通。直到我不得不使用类类型的 Lang 对象(我可以完全控制它),并且这个类重载了 operator[]。如果我使用此解决方法,过载将停止工作,并出现错误:
无法将带有 [] 的索引应用于“对象”类型的表达式
注意如果我只是将 Lang 属性包含在常规匿名对象中,则索引工作得非常好。
我还尝试为所有模型创建一个单独的基类,声明该类中的所有公共属性,但也从 System.Dynamic.DynamicObject 派生它,并覆盖将动态查找的 TryGetMember 方法字典中的页面属性(这会起作用,因为这些属性通常是简单的对象,即它们不会覆盖索引运算符,因此我可以在运行时动态添加这些属性:
var pageModel = new {
PropX = "foo",
PropY = "bar"
};
var model = CreateMainModel(pageModel);
public object CreateMainModel(object pageModel)
{
var mainModel = new BaseModel()
mainModel.Lang = _lang;
foreach (System.Reflection.PropertyInfo fi in pageModel.GetType().GetProperties())
{
mainModel.PageProperties[fi.Name] = fi.GetValue(pageModel, null);
}
return mainModel;
}
class BaseModel : DynamicObject
{
public LanguageMap Lang { get; set; }
public Dictionary<string, object> PageProperties { get; set; }
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (PageProperties.ContainsKey(binder.Name))
{
result = PageProperties[binder.Name];
return true;
}
return false;
}
}
这样做的问题是,在派生自 DynamicObject 的类中声明的属性在页面模板中不可见,似乎只有从 TryGetMember 返回的属性是可见的。如果我让成员在 TryGetMember 中显式检查他们的名字可见,那么索引将停止工作,就像上面的情况一样。
如果这是 C++,我可以创建一个预处理器宏:
#define COMMON_MODEL_PROPERTIES \
Lang = _lang
More = _otherProperty
// Where models are declared:
var model = new
{
COMMON_MODEL_PROPERTIES,
PropX = "foo",
PropY = "bar"
}
但这不是 C++……这是 C#。
有什么想法吗?
【问题讨论】:
-
我认为您使用组合的直觉可能是正确的,下面的问题是因为我很好奇会提出什么建议
-
哇,真是一团糟。如果 javascript 开发人员决定使用 C#,这看起来有点像会发生的事情。我认为我的第一个行动方案是交出通知并逃跑
-
怎么样..
records?易于声明和实例化,在语义上与 AT(可变性、等价性)非常相似,可以参与继承层次结构,查找/替换可能会做很多笨拙的工作...... -
一堆样板代码或在运行时使用一些 hacky 反射工作让你的脚被吹走,很难的
标签: c# templates properties anonymous-class dynamicobject