C# 确定继承接口类类型的最佳方法是什么?
在我的应用程序中,我使用criterias。我有一个基本标准接口和从该基本接口继承的其他接口: ICriteria | | ---------------------- | | ITextCriteria IChoices 我的问题是,这是最好的方式吗?还是有更好的方法来实现这一点 很有可能会有更多基于ICriteria的接口 编辑: 我有两种类型的控件,我想动态添加到我的应用程序中。一个控件是文本框,另一个是单选按钮 对于单选按钮,用户可以定义选项。定义选项后,用户必须选择其中一个选项,并且所选选项必须保存在数据库中,该数据库稍后用于执行搜索操作。因此,当单击Save按钮时,我必须确定所选的类型radio或text,如果是radio,则保存答案 对于文本框,这没有任何答案的可能性。因此,它有一个不同的接口 我希望我现在能说清楚一点。这是另一个相关的问题: 编辑二: 这就是我的方法SaveMultipleEchoiceValues的样子:C# 确定继承接口类类型的最佳方法是什么?,c#,interface,C#,Interface,在我的应用程序中,我使用criterias。我有一个基本标准接口和从该基本接口继承的其他接口: ICriteria | | ---------------------- | | ITextCriteria IChoices 我的问题是,这是最好的方式吗?还是有更好的方法来实现这一点 很有可能会有更多基于ICriteria的接口 编辑:
private void SaveMultipleChoiceValues(IChoices criteria)
{
foreach (DataGridViewRow row in dgvCriteriaControls.Rows)
{
if (row == dgvCriteriaControls.Rows[dgvCriteriaControls.Rows.Count - 1])
continue;
//multipleChoice.AddChoice(row.Cells["Name"].Value.ToString());
string choice = row.Cells["Name"].Value.ToString();
criteria.AddChoice(choice);
}
}
这似乎是多态性的一个主要例子 与其尝试在ICriteria实现上进行类型切换,不如在ICriteria中添加一个方法,或者在所有ICriteria实现的某个公共基类中添加一个虚拟方法,然后调用它 显然,此方法的实现将需要访问不属于您的ICriteria实例的对象,但根据场景的具体情况,您可以使用其他设计模式来解决这个问题 更新: 下面是一个完整的解决方案,包含您发布的代码: 创建一个新界面ICriteriaView,该界面将为案例中的视图建模,并显示ICriteria。表单需要根据条件实现的确切接口进行一些处理,因此为代码中存在的每个接口添加一个带有一个重载的方法。不要为ICriteria本身添加重载。[1] 您的表单将实现此接口,并为ICriteria的每个子类型提供适当处理的方法:
class MyForm : ICriteriaView {
public void ProcessCriteria(IChoices criteria) {
this.SaveMultipleChoiceValues(criteria);
}
public void ProcessCriteria(ITextCriteria criteria) {
// do nothing
}
private void SaveMultipleChoiceValues(IChoices criteria)
{
foreach (DataGridViewRow row in dgvCriteriaControls.Rows)
{
if (row == dgvCriteriaControls.Rows[dgvCriteriaControls.Rows.Count - 1])
continue;
//multipleChoice.AddChoice(row.Cells["Name"].Value.ToString());
string choice = row.Cells["Name"].Value.ToString();
criteria.AddChoice(choice);
}
}
}
ICriteria的每个实现都需要实现一个方法,该方法为其类型调用适当的ICriteriaView重载。这就是重定向魔法发生的地方:我们将使用多态性让编译器发现对象的实际类型,然后在ICriteriaView.ProcessCriteria上使用方法重载来访问适当的代码
interface ICriteria {
void PerformProcessingOn(ICriteriaView view);
}
interface IChoices : ICriteria {
}
interface ITextCriteria : ICriteria {
}
这就是调度到适当过载的地方:
class MultipleChoice : IChoices {
public PerformProcessingOn(ICriteriaView view) {
view.ProcessCriteria(this);
}
}
class SimpleInput : ITextCriteria {
public PerformProcessingOn(ICriteriaView view) {
view.ProcessCriteria(this);
}
}
然后,您的代码将执行以下操作:
// Get selected criteria
var selectedCriteria = cmbType.SelectedItem as ICriteria;
// Here's where polymorphism kicks in
selectedCriteria.PerformProcessingOn(this);
// Finally, code that runs the same for all objects
_category.AddCriteria(selectedCriteria);
selectedCriteria.LabelText = txtLabeltext.Text;
this.Close();
维护:
每当您添加新的ICriteria子接口实现时,ICriteria的定义将强制您在其上实现PerformProcessingOn方法。在该方法中,您真正能做的就是调用view.ProcessCriteriathis。反过来,这将迫使您在ICriteriaView和MyForm中实现适当的ProcessCriteria重载
因此,我们实现了两个重要目标:
编译器将不允许您添加新的ICriteria实现,而不指定该实现应如何与ICriteriaView交互。
从源代码中很容易发现MyView在读取多回音代码时对例如IChoices的具体操作。代码的结构会自动引导您找到MyForm.SaveMultipleChiceValues。
注:
[1] 选择是否为ICriteria本身添加重载实际上是一种折衷:
如果确实添加了一个,则代码如下:
class MultipleChoice : IChoices {
public PerformProcessingOn(ICriteriaView view) {
view.ProcessCriteria(this);
}
}
将始终成功编译,因为即使没有ICriteriaView.ProcessCriteriaIChoices重载,编译器仍可以使用ICriteriaView.ProcessCriteriaICriteria重载
这意味着,当添加新的ICriteria子接口实现时,编译器将不再强制您检查ICriteriaView.ProcessCriteriaICriteria的实现是否真的适合您的新实现
如果不添加,则在编写view.ProcessCriteriathis时;编译器将强制您检查并相应地更新ICriteriaView和MyForm
在这种情况下,根据您提供的信息,我相信最后一个选择是正确的
[2] 如上所示,MultipleChice和SimpleInput内部的ICriteria.PerformProcessing的实现看起来完全相同。如果这两个类有一个在实践中非常可能的公共基,那么您可能会尝试将重复的代码移动到该基中。不要那样做;这将导致解决方案中断
棘手的部分是当你查看时,在MultipleChice内部;编译器可以推断出它的静态类型是IChoices
-这就是重定向发生的地方!如果将对ProcessCriteria的调用移动到假设的基类CriteriaBase:ICriteria中,则该类型将成为ICriteria,并且将调用分派到相应的ICriteriaView.ProcessCriteria重载将不再工作。这看起来像是多态性的一个主要示例 与其尝试在ICriteria实现上进行类型切换,不如在ICriteria中添加一个方法,或者在所有ICriteria实现的某个公共基类中添加一个虚拟方法,然后调用它 显然,此方法的实现将需要访问不属于您的ICriteria实例的对象,但根据场景的具体情况,您可以使用其他设计模式来解决这个问题 更新: 下面是一个完整的解决方案,包含您发布的代码: 创建一个新界面ICriteriaView,该界面将为案例中的视图建模,并显示ICriteria。表单需要根据条件实现的确切接口进行一些处理,因此为代码中存在的每个接口添加一个带有一个重载的方法。不要为ICriteria本身添加重载。[1] 您的表单将实现此接口,并为ICriteria的每个子类型提供适当处理的方法:
class MyForm : ICriteriaView {
public void ProcessCriteria(IChoices criteria) {
this.SaveMultipleChoiceValues(criteria);
}
public void ProcessCriteria(ITextCriteria criteria) {
// do nothing
}
private void SaveMultipleChoiceValues(IChoices criteria)
{
foreach (DataGridViewRow row in dgvCriteriaControls.Rows)
{
if (row == dgvCriteriaControls.Rows[dgvCriteriaControls.Rows.Count - 1])
continue;
//multipleChoice.AddChoice(row.Cells["Name"].Value.ToString());
string choice = row.Cells["Name"].Value.ToString();
criteria.AddChoice(choice);
}
}
}
ICriteria的每个实现都需要实现一个方法,该方法为其类型调用适当的ICriteriaView重载。这就是重定向魔法发生的地方:我们将使用多态性让编译器发现对象的实际类型,然后在ICriteriaView.ProcessCriteria上使用方法重载来访问适当的代码
interface ICriteria {
void PerformProcessingOn(ICriteriaView view);
}
interface IChoices : ICriteria {
}
interface ITextCriteria : ICriteria {
}
这就是调度到适当过载的地方:
class MultipleChoice : IChoices {
public PerformProcessingOn(ICriteriaView view) {
view.ProcessCriteria(this);
}
}
class SimpleInput : ITextCriteria {
public PerformProcessingOn(ICriteriaView view) {
view.ProcessCriteria(this);
}
}
然后,您的代码将执行以下操作:
// Get selected criteria
var selectedCriteria = cmbType.SelectedItem as ICriteria;
// Here's where polymorphism kicks in
selectedCriteria.PerformProcessingOn(this);
// Finally, code that runs the same for all objects
_category.AddCriteria(selectedCriteria);
selectedCriteria.LabelText = txtLabeltext.Text;
this.Close();
维护:
每当您添加新的ICriteria子接口实现时,ICriteria的定义将强制您在其上实现PerformProcessingOn方法。在该方法中,您真正能做的就是调用view.ProcessCriteriathis。反过来,这将迫使您在ICriteriaView和MyForm中实现适当的ProcessCriteria重载
因此,我们实现了两个重要目标:
编译器将不允许您添加新的ICriteria实现,而不指定该实现应如何与ICriteriaView交互。
从源代码中很容易发现MyView在读取多回音代码时对例如IChoices的具体操作。代码的结构会自动引导您找到MyForm.SaveMultipleChiceValues。
注:
[1] 选择是否为ICriteria本身添加重载实际上是一种折衷:
如果确实添加了一个,则代码如下:
class MultipleChoice : IChoices {
public PerformProcessingOn(ICriteriaView view) {
view.ProcessCriteria(this);
}
}
将始终成功编译,因为即使没有ICriteriaView.ProcessCriteriaIChoices重载,编译器仍可以使用ICriteriaView.ProcessCriteriaICriteria重载
这意味着,当添加新的ICriteria子接口实现时,编译器将不再强制您检查ICriteriaView.ProcessCriteriaICriteria的实现是否真的适合您的新实现
如果不添加,则在编写view.ProcessCriteriathis时;编译器将强制您检查并相应地更新ICriteriaView和MyForm
在这种情况下,根据您提供的信息,我相信最后一个选择是正确的
[2] 如上所示,MultipleChice和SimpleInput内部的ICriteria.PerformProcessing的实现看起来完全相同。如果这两个类有一个在实践中非常可能的公共基,那么您可能会尝试将重复的代码移动到该基中。不要那样做;这将导致解决方案中断
棘手的部分是当你查看时,在MultipleChice内部;编译器可以推断这是IChoices的静态类型-这是重定向发生的地方!如果将对ProcessCriteria的调用移动到假设的基类CriteriaBase:ICriteria中,则该类型将变为ICriteria,并且将调用分派到相应的ICriteriaView.ProcessCriteria重载将不再工作。这不是最好的方法。如果您根据对象的类型执行不同的操作,那么您可能应该使用多态性,原因有很多 您如何使用多态性取决于您根据所使用的不同类型的ICriteria实际需要做什么。如果您只需要获取包含其所有成员的字符串,您可以轻松地向ICriteria添加一个方法,并将责任交给类本身,而不是依赖于它的代码。这样可以减少重复,将代码放在合理的位置,并确保您不会忘记添加co 这是一种新型的ICriteria
如果您向我们提供更多关于您希望不同类型的人如何对待/表现的信息,我们可能会向您提供更具体的建议:这不是最好的办法。如果您根据对象的类型执行不同的操作,那么您可能应该使用多态性,原因有很多 您如何使用多态性取决于您根据所使用的不同类型的ICriteria实际需要做什么。如果您只需要获取包含其所有成员的字符串,您可以轻松地向ICriteria添加一个方法,并将责任交给类本身,而不是依赖于它的代码。这样可以减少重复,将代码放在逻辑位置,并确保您不会忘记为新类型的ICriteria添加代码 如果您向我们提供更多关于您希望不同类型的人如何对待/表现的信息,我们可能会向您提供更具体的建议:你可以这样做:
var selectedCriteria = cmbType.SelectedItem as ICriteria;
if (typeof(IChoices).IsAssignableFrom(selectedCriteria.GetType()))
{
IChoices criteria = selectedCriteria as IChoices;
SaveMultipleChoiceValues(criteria);
}
else if(typeof(ITextCriteria).IsAssignableFrom(selectedCriteria.GetType()))
{
if (selectedCriteria.GetCriteriaType() == CriteriaTypes.None)
{
return;
}
}
但是多态性可能是你最好的选择。你可以这样做:
var selectedCriteria = cmbType.SelectedItem as ICriteria;
if (typeof(IChoices).IsAssignableFrom(selectedCriteria.GetType()))
{
IChoices criteria = selectedCriteria as IChoices;
SaveMultipleChoiceValues(criteria);
}
else if(typeof(ITextCriteria).IsAssignableFrom(selectedCriteria.GetType()))
{
if (selectedCriteria.GetCriteriaType() == CriteriaTypes.None)
{
return;
}
}
但是多态性可能是你最好的选择。这里有一个长期解决方案,可以解决不断扩大的标准列表,而不必添加更多的if/then/else 虽然此代码对于不习惯以这种方式进行设计的人来说很复杂,但它允许您保持处理条件的方法不变,并且只注册新委托来处理其他条件 其思想是创建一个类型对象的映射,其中包含要执行的委托。然后,您可以注册新的委托,以便在生成它们时基于新类型执行它们
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Stackoverflow_4527626
{
delegate void CriteraDelegate(params object[] args);
class CriteraManager
{
private Dictionary<Type, CriteraDelegate> criterian = new Dictionary<Type, CriteraDelegate>();
public void RegisterCritera(Type type, CriteraDelegate del)
{
criterian[type] = del;
}
public void Execute(Object criteria, params object[] args)
{
Type type = criteria.GetType();
/// Check to see if the specific type
/// is in the list.
if (criterian.ContainsKey(type))
{
criterian[type](args);
}
/// If it isn't perform a more exhaustive search for
/// any sub types.
else
{
foreach (Type keyType in criterian.Keys)
{
if (keyType.IsAssignableFrom(type))
{
criterian[keyType](args);
return;
}
}
throw new ArgumentException("A delegate for Type " + type + " does not exist.");
}
}
}
interface InterfaceA { }
interface InterfaceB1 : InterfaceA { }
interface InterfaceB2 : InterfaceA { }
interface InterfaceC { }
class ClassB1 : InterfaceB1 { }
class ClassB2 : InterfaceB2 { }
class ClassC : InterfaceC { }
class Program
{
static void ExecuteCritera1(params object[] args)
{
Console.WriteLine("ExecuteCritera1:");
foreach (object arg in args)
Console.WriteLine(arg);
}
static void ExecuteCritera2(params object[] args)
{
Console.WriteLine("ExecuteCritera2:");
foreach (object arg in args)
Console.WriteLine(arg);
}
static void Main(string[] args)
{
CriteraDelegate exampleDelegate1 = new CriteraDelegate(ExecuteCritera1);
CriteraDelegate exampleDelegate2 = new CriteraDelegate(ExecuteCritera2);
CriteraManager manager = new CriteraManager();
manager.RegisterCritera(typeof(InterfaceB1), exampleDelegate2);
manager.RegisterCritera(typeof(InterfaceB2), exampleDelegate2);
manager.RegisterCritera(typeof(InterfaceC), exampleDelegate1);
ClassB1 b1 = new ClassB1();
ClassB2 b2 = new ClassB2();
ClassC c = new ClassC();
manager.Execute(b1, "Should execute delegate 2");
manager.Execute(b2, "Should execute delegate 2");
manager.Execute(c, "Should execute delegate 1");
}
}
}
这里有一个长期解决方案,可以解决不断扩大的准则列表,而不必添加更多的if/then/else 虽然此代码对于不习惯以这种方式进行设计的人来说很复杂,但它允许您保持处理条件的方法不变,并且只注册新委托来处理其他条件 其思想是创建一个类型对象的映射,其中包含要执行的委托。然后,您可以注册新的委托,以便在生成它们时基于新类型执行它们
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Stackoverflow_4527626
{
delegate void CriteraDelegate(params object[] args);
class CriteraManager
{
private Dictionary<Type, CriteraDelegate> criterian = new Dictionary<Type, CriteraDelegate>();
public void RegisterCritera(Type type, CriteraDelegate del)
{
criterian[type] = del;
}
public void Execute(Object criteria, params object[] args)
{
Type type = criteria.GetType();
/// Check to see if the specific type
/// is in the list.
if (criterian.ContainsKey(type))
{
criterian[type](args);
}
/// If it isn't perform a more exhaustive search for
/// any sub types.
else
{
foreach (Type keyType in criterian.Keys)
{
if (keyType.IsAssignableFrom(type))
{
criterian[keyType](args);
return;
}
}
throw new ArgumentException("A delegate for Type " + type + " does not exist.");
}
}
}
interface InterfaceA { }
interface InterfaceB1 : InterfaceA { }
interface InterfaceB2 : InterfaceA { }
interface InterfaceC { }
class ClassB1 : InterfaceB1 { }
class ClassB2 : InterfaceB2 { }
class ClassC : InterfaceC { }
class Program
{
static void ExecuteCritera1(params object[] args)
{
Console.WriteLine("ExecuteCritera1:");
foreach (object arg in args)
Console.WriteLine(arg);
}
static void ExecuteCritera2(params object[] args)
{
Console.WriteLine("ExecuteCritera2:");
foreach (object arg in args)
Console.WriteLine(arg);
}
static void Main(string[] args)
{
CriteraDelegate exampleDelegate1 = new CriteraDelegate(ExecuteCritera1);
CriteraDelegate exampleDelegate2 = new CriteraDelegate(ExecuteCritera2);
CriteraManager manager = new CriteraManager();
manager.RegisterCritera(typeof(InterfaceB1), exampleDelegate2);
manager.RegisterCritera(typeof(InterfaceB2), exampleDelegate2);
manager.RegisterCritera(typeof(InterfaceC), exampleDelegate1);
ClassB1 b1 = new ClassB1();
ClassB2 b2 = new ClassB2();
ClassC c = new ClassC();
manager.Execute(b1, "Should execute delegate 2");
manager.Execute(b2, "Should execute delegate 2");
manager.Execute(c, "Should execute delegate 1");
}
}
}
你想达到什么目标?如果你需要以这种方式找出类型,你可能做错了什么。你想达到什么目的?如果您需要以这种方式查找类型,则可能是做错了什么。如果我将方法SaveMultipleChiceValues添加到ICriteria,那么当条件为textbox时,我也可以调用此方法,但textbox没有答案。所以这是错误的。这就是为什么我创建了一个继承自ICriteria@Martijn:我加了一个例子,看一看。@Jon:这个解决方案把我的头脑搞乱了,呵呵。这对我来说是一种新的方法。我对这种解决方案没有太多经验。不过,我觉得这很有趣。所以,谢谢你。但我不能解决这个难题。我知道你为我做了很多,但我不知道userInterfaceHelper从var userInterfaceHelper=…;//中得到了什么取决于您的应用程序及其初始化方式。@Martijn:userInterfaceHelper将执行SaveMultipleChiceValues在代码中执行的任何操作。把你的代码移到那边去。@Jon:我现在觉得很愚蠢:$我不明白。savemultipleechoicevalues是一种使用答案可能性在网格中循环的方法。我是否必须执行var userInterfaceHelper=savemultipleechoicevaluescriteria;?很抱歉,我不明白。如果我将方法savemultipleechoicevalues添加到ICriteria,那么当条件是文本框时,我也可以调用此方法,但是文本框没有答案的可能性。所以这是错误的。这就是为什么我创建了一个继承自ICriteria@Martijn:我加了一个例子,看一看。@Jon:这个解决方案把我的头脑搞乱了,呵呵。这对我来说是一种新的方法。我对这种解决方案没有太多经验。不过,我觉得这很有趣。所以,谢谢你。但我不能解决这个难题。我知道你为我做了很多,但我不知道userInterfaceHelper从var userInterfaceHelper=…;//中得到了什么取决于您的应用程序及其初始化方式。@Martijn:userInterfaceHelper将执行SaveMultipleChiceValues在代码中执行的任何操作。把你的代码移到那边去。@Jon:我现在觉得很愚蠢:$我不明白。savemultipleechoicevalues是一种使用答案可能性在网格中循环的方法。我是否必须执行var userInterfaceHelper=savemultipleechoicevaluescriteria;?对不起,我不明白..在我可以给别人留言之前,我会给自己的帖子添加评论。我不建议在ICriteria中添加方法。ICriteria似乎是一个包含数据的模型类型对象,但与对critera采取的操作无关。如果向ICriteria添加了与对其执行的操作相关的代码,则开发人员已将ICriteria锁定为仅在其特定世界中使用。ICriteria可以设计成可以在其他地方使用,并且可以根据需要进行不同的操作
在程序中使用ICriteria的位置。你真的不想把控制器代码放在模型代码中。我认为你的推理有一些错误:向ICriteria添加方法与向ICriteria添加代码不同,因为代码==逻辑,但方法可能只是一种机制,不包含逻辑。b正如您在我的回答中所看到的,没有必要将iCiteria与具体实施细节结合起来;通用编程实践可以很好地实现可扩展性。最后,你提出的解决方案有一个严重的缺点:DIY多态性非常脆弱。在我可以向其他人发表评论之前,请在我自己的帖子中添加评论。我不建议在ICriteria中添加方法。ICriteria似乎是一个包含数据的模型类型对象,但与对critera采取的操作无关。如果向ICriteria添加了与对其执行的操作相关的代码,则开发人员已将ICriteria锁定为仅在其特定世界中使用。ICriteria可以设计成可以在其他地方使用,并且可以根据程序中使用ICriteria的位置执行不同的操作。你真的不想把控制器代码放在模型代码中。我认为你的推理有一些错误:向ICriteria添加方法与向ICriteria添加代码不同,因为代码==逻辑,但方法可能只是一种机制,不包含逻辑。b正如您在我的回答中所看到的,没有必要将iCiteria与具体实施细节结合起来;通用编程实践可以很好地实现可扩展性。最后,您提出的解决方案有一个严重的缺点:DIY多态性非常脆弱。