【问题标题】:Passing a generic <TObject> class to a form将通用 <TObject> 类传递给表单
【发布时间】:2017-08-02 13:34:07
【问题描述】:

我似乎无法通过搜索找到答案,所以这里......

我知道我可以通过使用这种类型的代码将 Class 对象通用地传递给其他类:

public class ClsGeneric<TObject> where TObject : class
{
    public TObject GenericType { get; set; }
}

然后这样构造:

ClsGeneric<MyType> someName = new ClsGeneric<MyType>()

但是,我有一个应用程序需要我打开一个表单并以某种方式传入泛型类型以在该表单中使用。我试图能够将此表单重新用于许多不同的类类型。

有谁知道这是否可行,如果可以怎么办?

我对 Form 构造函数进行了一些试验,但没有成功。

提前非常感谢, 戴夫

更新:澄清我想要达到的结果是

更新:8 月 4 日,我已经前进了一点,但我提供了解决方案的赏金。这是我现在拥有的:

interface IFormInterface
{
    DialogResult ShowDialog();
}


public class FormInterface<TObject> : SubForm, IFormInterface where TObject : class
{ }

public partial class Form1 : Form
{
    private FormController<Parent> _formController;

    public Form1()
    {
        InitializeComponent();
            _formController = new FormController<Parent>(this.btnShowSubForm, new DataController<Parent>(new MeContext()));   
    }
}

public class FormController<TObject> where TObject : class
{
    private DataController<TObject> _dataController;
    public FormController(Button btn, DataController<TObject> dataController)
    {
        _dataController = dataController;
        btn.Click += new EventHandler(btnClick);
    }

    private void btnClick(object sender, EventArgs e)
    {
        showSubForm("Something");
    }

    public void showSubForm(string className)
    {
        //I'm still stuck here because I have to tell the interface the Name of the Class "Child", I want to pass <TObject> here.
        // Want to pass in the true Class name to FormController from the MainForm only, and from then on, it's generic.

        IFormInterface f2 = new FormInterface<Child>();
        f2.ShowDialog();
    }
}

class MeContext : DbContext
{
    public MeContext() : base(@"data source=HAZEL-PC\HAZEL_SQL;initial catalog=MCL;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework") { }
    public DbSet<Parent> Child { get; set; }
}

public class DataController<TObject> where TObject : class
{
    protected DbContext _context;

    public DataController(DbContext context)
    {
        _context = context;
    }
}

public class Parent
{
    string Name { get; set; }
    bool HasChildren { get; set; }
    int Age { get; set; }
}

public class Child
{
    string Name { get; set; }
    int Age { get; set; }
}

【问题讨论】:

  • 您想将泛型类型传递给您的表单并让您的表单实例化该类型?
  • “以某种方式传入泛型类型以用于该形式” - 有什么用?这种类型将如何使用?
  • 次要注意:不要在班级名称前加上Cls,这是非常古老的学校行为,不推荐。
  • 如果你想在你的泛型类中实例化 T 你需要你的约束在哪里 T : class, new()
  • 我真的不明白你为什么在这里需要泛型。无论如何,子表单对 TObject 有什么作用?

标签: c# forms generic-programming tobject


【解决方案1】:

也许你已经尝试过了,但你可以创建一个自定义类:

public class GenericForm<TObject> : Form where TObject : class
{
    // Here you can do whatever you want,
    // exactly like the example code in the
    // first lines of your question
    public TObject GenericType { get; set; }

    public GenericForm()
    {
        // To show that this actually works,
        // I'll handle the Paint event, because
        // it is executed AFTER the window is shown.
        Paint += GenericForm_Paint;
    }

    private void GenericForm_Paint(object sender, EventArgs e)
    {
        // Let's print the type of TObject to see if it worked:
        MessageBox.Show(typeof(TObject).ToString());
    }
}

如果你像这样创建它的一个实例:

var form = new GenericForm<string>();
form.Show();

结果是:

更进一步,您可以使用Activator 类从GenericForm 类中创建TObject 类型的实例:

GenericType = (TObject)Activator.CreateInstance(typeof(TObject));

在这个例子中,既然我们知道这是一个字符串,我们也知道它应该抛出一个异常,因为字符串没有无参数的构造函数。所以,让我们改用 char 数组 (char[]) 构造函数:

GenericType = (TObject)Activator.
         CreateInstance(typeof(TObject), new char[] { 'T', 'e', 's', 't' });

MessageBox.Show(GenericType as string);

结果:

那我们做作业吧。下面的代码应该可以实现你想要的。

public class Parent
{
    string Name { get; set; }
    bool HasChildren { get; set; }
    int Age { get; set; }
}

public class Child
{
    string Name { get; set; }
    int Age { get; set; }
}

public class DataController<TObject> where TObject : class
{
    protected DbContext _context;

    public DataController(DbContext context)
    {
        _context = context;
    }
}

public class FormController<TObject> where TObject : class
{
    private DataController<TObject> _dataController;

    public FormController(Button btn, DataController<TObject> dataController)
    {
        _dataController = dataController;
        btn.Click += new EventHandler(btnClick);
    }

    private void btnClick(object sender, EventArgs e)
    {
        GenericForm<TObject> form = new GenericForm<TObject>();
        form.ShowDialog();
    }
}

public class GenericForm<TObject> : Form where TObject : class
{
    public TObject GenericType { get; set; }

    public GenericForm()
    {
        Paint += GenericForm_Paint;
    }

    private void GenericForm_Paint(object sender, EventArgs e)
    {
        MessageBox.Show(typeof(TObject).ToString());

        // If you want to instantiate:
        GenericType = (TObject)Activator.CreateInstance(typeof(TObject));
    }
}

但是,查看您当前的示例,您有两个类,ParentChild。如果我理解正确,那是TObject 类型的唯一可能性。

如果是这种情况,那么如果有人将string 作为类型参数传递(当执行到达Activator.CreateInstance),上面的代码就会爆炸——出现运行时异常(因为string 没有无参数构造函数):

为了保护您的代码免受这种情况的影响,我们可以在可能的类中继承一个接口。这将导致编译时异常,这是可取的:

代码如下。

// Maybe you should give a better name to this...
public interface IAllowedParamType { }

// Inherit all the possible classes with that
public class Parent : IAllowedParamType
{
    string Name { get; set; }
    bool HasChildren { get; set; }
    int Age { get; set; }
}

public class Child : IAllowedParamType
{
    string Name { get; set; }
    int Age { get; set; }
}

// Filter the interface on the 'where'
public class DataController<TObject> where TObject : class, IAllowedParamType
{
    protected DbContext _context;

    public DataController(DbContext context)
    {
        _context = context;
    }
}

public class FormController<TObject> where TObject : class, IAllowedParamType
{
    private DataController<TObject> _dataController;

    public FormController(Button btn, DataController<TObject> dataController)
    {
        _dataController = dataController;
        btn.Click += new EventHandler(btnClick);
    }

    private void btnClick(object sender, EventArgs e)
    {
        GenericForm<TObject> form = new GenericForm<TObject>();
        form.ShowDialog();
    }
}

public class GenericForm<TObject> : Form where TObject : class, IAllowedParamType
{
    public TObject GenericType { get; set; }

    public GenericForm()
    {
        Paint += GenericForm_Paint;
    }

    private void GenericForm_Paint(object sender, EventArgs e)
    {
        MessageBox.Show(typeof(TObject).ToString());

        // If you want to instantiate:
        GenericType = (TObject)Activator.CreateInstance(typeof(TObject));
    }
}

更新

正如 RupertMorrish 所说,您仍然可以编译以下代码:

public class MyObj : IAllowedParamType
{
    public int Id { get; set; }

    public MyObj(int id)
    {
        Id = id;
    }
}

这仍然应该引发异常,因为您刚刚删除了隐式无参数构造函数。当然,如果你知道自己在做什么,这很难发生,但是我们可以通过在 'where' 类型过滤上使用 new() 来禁止这种情况 - 同时也摆脱 Activator.CreateInstance 的东西。

整个代码:

// Maybe you should give a better name to this...
public interface IAllowedParamType { }

// Inherit all the possible classes with that
public class Parent : IAllowedParamType
{
    string Name { get; set; }
    bool HasChildren { get; set; }
    int Age { get; set; }
}

public class Child : IAllowedParamType
{
    string Name { get; set; }
    int Age { get; set; }
}

// Filter the interface on the 'where'
public class DataController<TObject> where TObject : new(), IAllowedParamType
{
    protected DbContext _context;

    public DataController(DbContext context)
    {
        _context = context;
    }
}

public class FormController<TObject> where TObject : new(), IAllowedParamType
{
    private DataController<TObject> _dataController;

    public FormController(Button btn, DataController<TObject> dataController)
    {
        _dataController = dataController;
        btn.Click += new EventHandler(btnClick);
    }

    private void btnClick(object sender, EventArgs e)
    {
        GenericForm<TObject> form = new GenericForm<TObject>();
        form.ShowDialog();
    }
}

public class GenericForm<TObject> : Form where TObject : new(), IAllowedParamType
{
    public TObject GenericType { get; set; }

    public GenericForm()
    {
        Paint += GenericForm_Paint;
    }

    private void GenericForm_Paint(object sender, EventArgs e)
    {
        MessageBox.Show(typeof(TObject).ToString());

        // If you want to instantiate:
        GenericType = new TObject();
    }
}

【讨论】:

  • 非常感谢您花时间提供解决方案,非常感谢。我将查看我收到的所有解决方案,并将在接下来的几天内回复。
  • /// (因为字符串没有无参数构造函数)您可以通过指定 ... 将 TObject 限制为具有无参数构造函数的类型 其中 TObject : IAllowedParamType, new() [new() 表示班级。如果你喜欢,你可以留下它]
  • @RupertMorrish 好主意。但问题是,应用程序的业务逻辑中不希望出现的任何类型(甚至是一些具有无参数构造函数的类型)都不应被允许,这可以使用通用接口来解决。不过,很好地提及new(),+1。
  • 好吧,我假设你也有这个界面。 new() 是除此之外的,因为您不能在接口中指定构造函数。
  • 如果你指定new(),那么它不会编译。 docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
【解决方案2】:

我想你可以给FormController添加一个新的类型参数:

public class FormController<TParent, TChild>
  where TParent : class
  where TChild : class 
{
    ...

    public void showSubForm(string className)
    {
        IFormInterface f2 = new FormInterface<TChild>();
        f2.ShowDialog();
    }
}

【讨论】:

  • 非常感谢您花时间提供解决方案,非常感谢。我将查看我收到的所有解决方案,并将在接下来的几天内回复。
【解决方案3】:

据我了解,您希望Form&lt;T&gt;MainForm 中的某些操作时打开,而您的MainForm 使用FormController 作为所有表单的管理器,传递通用类型信息给你的Form&lt;T&gt;。此外,您的Form&lt;T&gt; 类的实例化对象应该从您的FormController 请求一个DatabaseController&lt;T&gt; 类的实例。

如果是这样,以下尝试可能会奏效:

MainForm 在构造函数初始化时收到对FormController 实例的引用,或者有其他方式与FormController 交互,例如CommonService 两者都知道,等等。

这允许MainForm 调用FormController 的通用方法来创建和显示一个新的Form 对象:

void FormController.CreateForm<T> () 
{
    Form<T> form = new Form<T>();
    form.Show();
    // Set potential Controller states if not stateless
    // Register forms, etc.
}

Form&lt;T&gt; 类似:

class Form<T> : Form where T : class
{
    DatabaseController<T> _dbController;
    Form(FormController formController)
    {
        _dbController = formController.CreateDatabaseController<T>();
    }
}

现在您有几种方法可以让表单接收 DatabaseController 实例:

1. 您的Form&lt;T&gt; 接收到FormController 的引用或有其他方式与之通信以调用类似以下方法的方法:
DatabaseController<T> FormController.CreateDatabaseController<T> () 
{
    return new DatabaseController<T>();
}

您的FormController 不需要是通用的,否则您需要为每个 T 提供一个新的 FormController 实例。它只需要提供一个泛型方法。

  1. 您的 Form&lt;T&gt; 在构造函数初始化时从 FormController 接收 DatabaseController 的实例:

    void FormController.CreateForm() { 表单 form = new Form(new DatabaseController()); form.Show(); }

Form&lt;T&gt; 是:

class Form<T> : Form where T : class
{
    DatabaseController<T> _dbController;
    Form(DatabaseController<T> controller) 
    {
         _dbController = controller;
    }
}

3. 与 2 类似,但您的 Form&lt;T&gt;DatabaseController&lt;T&gt; 提供静态 FactoryMethods 以忠于 单一职责 原则。例如。:
public class Form<T> : Form where T : class
{
    private DatabaseController<T> _dbController;

    public static Form<T> Create<T>(DatabaseController<T> controller)
    {
        return new Form<T>(controller);
    }

    private Form(DatabaseController<T> controller) 
    {
         _dbController = controller;
    }
}

4. 您还可以使用IoC Container 在运行时注册和接收特定类型的实例。每个Form&lt;T&gt; 在运行时都会收到一个 IoC 容器实例,并请求其对应的DatabaseController&lt;T&gt;。这使您可以更好地管理控制器的生命周期并在应用程序中形成对象。

【讨论】:

  • 非常感谢您花时间提供解决方案,非常感谢。我将查看我收到的所有解决方案,并将在接下来的几天内回复。
【解决方案4】:

好吧,我不打算在这里详细介绍,仅对一些蓝图就足够了。 在这种情况下,我将结合使用 Unity 构造函数注入和泛型工厂来处理给定主表单类型的实例化。

没那么复杂,请查看 Unity 文档 Dependency Injection with Unity

从所有 DI 容器中选择 Unity 的原因是它是 Microsoft 本身企业库的一部分,现在继续以 Nugget 的形式作为独立库继续存在。我的一个朋友最近也将 Unity 移植到了 .Net 核心。简而言之,它是现有最精致的容器。

至于工厂,我认为这是必要的,因为您不想创建一个具体的查找来处理所有可能的类型,所以它显然必须是一个通用工厂。我建议您将您的工厂设为单例并将其放在另一个项目中,从而将您的 UI 项目与模型分开,双方将通过这个 DI 桥进行通信。您甚至可以更进一步,使用程序集反射处理您的模型类型。 抱歉太笼统了,但我真的不知道你对这些模式有多熟悉。真的值得花一些时间来利用这些模式。依我拙见,如果您想要一个真正可扩展的软件,就无法逃避这些操作。

如果您正在寻找有关实施上述任何策略的提示,您可以私下联系我。

【讨论】:

  • 非常感谢您花时间写出来。作为一种选择,我肯定会考虑一下,不,在你发帖之前我并不熟悉,再次非常感谢。
【解决方案5】:

尝试工厂方法。

public interface IProvider
{
    T GetObject<T>();
}

顶级表单:

public class TopLevelForm : Form
{
    public TopLevelForm(IProvider provider):base()
    {
         _provider = provider;
    }

    private void ShowSecondForm()
    {
        var f2 = new SecondForm(provider);
        f2.Show();
    }
}

二级表格:

public class SecondLevelForm : Form
{
    public SecondLevelForm(IProvider provider):base()
    {
         _data = provider.GetObject<MyEntity>();
    }
}

至于IProvider的实现-有很多方法,从最简单的return new T();开始

【讨论】:

  • 超级。我真的很感激,我会试一试的。
  • 对不起亚历克斯,不幸的是反过来,我的 SecondLevelForm 不知道 会是什么,只有顶级表单会。所以这似乎不起作用。谢谢,戴维
  • 我已经用图表更新了原始问题。非常感谢,戴夫
  • 我似乎不明白你到底想要实现什么,但从我的立场来看,它看起来越来越像 DataSource 用于绑定的属性,它是 object 类型。您可能也想使用这种方法,或者使用共享接口在 MainForm 和 SubForm 之间实现某种程度的类型安全。
猜你喜欢
  • 2012-08-17
  • 2020-01-09
  • 2020-05-08
  • 1970-01-01
  • 2014-02-02
  • 1970-01-01
  • 1970-01-01
  • 2013-01-09
  • 2015-11-27
相关资源
最近更新 更多