我们有一个应用程序,总体上被许多不同的客户用作“通用”产品,通常具有可供所有客户使用的相同功能。但是,有一些单独定制的组件,尤其是与从客户各自的内部系统导入数据有关的组件。
到目前为止,这主要意味着将不同的输入格式转换为相同的数据结构,但为某些类型保留额外的客户特定数据并在同样“了解”这一点的选定代码部分再次使用它变得越来越重要特定客户。在整个应用程序中,通常不会有很多不同的地方。
为了在不“污染”核心模型类的情况下做到这一点,“可扩展性”目前是通过让相关类派生自 DynamicObject
来实现的。然后用一些代码来动态添加用魔法字符串键入的属性,并在其他地方检查这些属性并检索它们,这甚至不是类型安全的,因为您需要知道将可能的值转换为什么。
我对此很不高兴,并想提出一个解决方案,感觉有点不像“猴子补丁”,而且在静态类型环境中更符合习惯。问题是实现这一目标的最佳选择是什么。 (附加要求是附加数据应该易于与对象的其余部分序列化/反序列化。)
- 面向对象语言中“显而易见”的事情当然是对模型类型进行子类化,但这会在对象创建和复制方面产生许多新问题,而且通常我不太喜欢(而且它只是违反了“继承之上的组合”)。
- 仍然使用继承但限制其影响的解决方案是添加单个属性
ExtensionData
到实际的类,它的类型是空的*ExtensionData
然后根据客户进行子分类的类。使用它确实涉及对实际扩展数据对象的一些类型检查和转换,但除此之外,它将是完全类型安全的。我认为这是我现在首选的方式。 - 与目前使用的动态方法非常相似的是用
Dictionary<string, object>
替换先前变体中的类型化对象。 .我认为这仍然比现在发生的情况要好,但它具有相同的基本问题,即动态类型化和使用魔术字符串(当然,即使它们将存储为常量)。
扩展数据实际上不是动态的;一旦知道要导入哪些客户数据,我们就处于一个定义明确的子域中,对导入的数据中的内容并不感到意外。
(通常情况下,这在 F# 中会更容易,我可以使用范围扩展属性或将 ExtensionData
作为 DU,其中每个案例都包含一个客户特定的记录类型,该记录类型将通过模式明确标识匹配。)
在 C# 中执行此操作的最“惯用”且可维护的方法是什么?是否有任何提到的替代方案具有我没有考虑过的巨大优势或缺陷?
最佳答案
你可以使用类似的东西
public class BaseClass
{
Dictionary<Type, object> propertys = new Dictionary<Type, object>();
public void Add<T>(T instance)
{
propertys[typeof(T)] = instance;
}
public T Get<T>()
{
return (T)propertys[typeof(T)];
}
public void Test()
{
Add<string>("Hello World");
string helloWorld = Get<string>();
}
}
限制是您只能为给定类型添加一个实例/属性。
如果性能不是问题,另一个解决方案可能如下所示。 但是请注意,使用这样的表达式很慢(呃)。你给它一个 lambda,但是隐式转换为 Expression 需要一些时间
using System.Linq.Expressions;
// There will never be an implementation of this interface
// Its only there to "define" the property names and types without
// magic strings and in a type safe way
public interface IBaseClassExtension
{
public string ExtensionProperty { get; set; }
public int ExtensionProperty2 { get; set; }
}
public class BaseClass
{
Dictionary<string, object> propertys = new Dictionary<string, object>();
public void Add<T, U>(Expression<Func<T, U>> expr, U instance)
{
var propExpr = expr.Body as MemberExpression;
// The declaring types name could be used in addition to be sure there
// is no naming conflict with members of other types that have the same member name
//string name = propExpr.Member.DeclaringType.FullName + propExpr.Member.Name;
propertys[propExpr.Member.Name] = instance;
}
public U Get<T, U>(Expression<Func<T, U>> expr)
{
var propExpr = expr.Body as MemberExpression;
//string name = propExpr.Member.DeclaringType.FullName + propExpr.Member.Name;
return (U)propertys[propExpr.Member.Name];
}
public void Test()
{
Add<IBaseClassExtension, string>(bce => bce.ExtensionProperty, "Hello World");
string helloWorld = Get<IBaseClassExtension, string>(bce => bce.ExtensionProperty);
}
}
这两种解决方案都可以以类型安全的方式使用,即使字典也只知道对象
如果您有时间等待 C# 6.0,新的 nameof 运算符应该比使用表达式快得多。但是你会失去一些类型安全性。
public interface IBaseClassExtension
{
public string ExtensionProperty { get; set; }
public int ExtensionProperty2 { get; set; }
}
public class BaseClass
{
Dictionary<string, object> propertys = new Dictionary<string, object>();
public void Add(string name, object instance)
{
propertys[name] = instance;
}
public T Get<T>(string name)
{
return (T)propertys[name];
}
public void Test()
{
Add(nameof(IBaseClassExtension.ExtensionProperty), "HelloWorld");
string helloWorld = Get<string>(nameof(IBaseClassExtension.ExtensionProperty));
}
}
编辑:
如果扩展属性/数据的数量不是那么多,另一种解决方案可能是使基本模型通用
public class BaseClass
{
}
public class BaseClass<T, U, V> : BaseClass
{
public T Extension1 { get; set; }
public U Extension2 { get; set; }
public V Extension3 { get; set; }
}
public class ClassThatIsUsingSpecificBaseClass
{
public void Test(BaseClass<int, int, string> baseClass)
{
//baseClass.Extension1 ...;
//baseClass.Extension2 ...;
}
}
public class ClassThatIsUsingBaseClass
{
public void Test(BaseClass baseClass)
{
}
}
这将是类型安全的,不涉及强制转换,并且只会派生一个 BaseClass,而不是针对每个独特的客户需求/数据。
关于c# - 为特定用例扩展基础模型类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30043319/