将通用 <TObject> class 传递给表单

Passing a generic <TObject> class to a form

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

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

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

然后这样构建:

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

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

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

我对 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; }
}

尝试工厂方法。

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();

也许您已经尝试过了,但您可以创建自定义 class:

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 class:[=34 从 GenericForm class 中创建类型 TObject 的实例=]

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

在这个例子中,既然我们知道这是一个字符串,我们也知道它应该抛出异常,因为字符串没有无参数构造函数。因此,让我们改用字符数组 (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));
    }
}

但是,查看您当前的示例,您有两个 class,ParentChild。如果我理解正确的话,这些是 TObject.

类型的唯一可能性

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

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

代码如下

// 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();
    }
}

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

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

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

因此,据我了解,您希望 Form<T>MainForm 中执行某些操作,而您的 MainForm 使用 FormController 作为经理在您的所有表单中,将通用类型信息传递给您的 Form<T>。此外,您的 Form<T> class 的实例化对象应该从您的 FormController.

请求一个 DatabaseController<T> class 的实例

如果是这种情况,以下尝试可能会奏效:

MainForm 在构造函数初始化时接收到对 FormController 实例的引用,或者有其他方式与 FormController 交互,例如a 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<T> 沿线:

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

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

1. 您的 Form<T> 接收到 FormController 的引用,或者有另一种与之通信的方式来调用类似于以下行的方法:

DatabaseController<T> FormController.CreateDatabaseController<T> () 
{
    return new DatabaseController<T>();
}

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

  1. 您的 Form<T> 在构造函数初始化时从 FormController 接收 DatabaseController 的实例:

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

其中 Form<T> 为:

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

3. 与 2 一样,但您的 Form<T>DatabaseController<T> 提供静态 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<T> 在运行时接收 IoC 容器的实例并请求其对应的 DatabaseController<T>。这使您可以更好地管理控制器的生命周期并在应用程序中形成对象。

好吧,我不会在这里深入细节,只需要一些蓝图就够了。 在这种情况下,我将结合使用 Unity 构造函数注入和通用工厂来处理给定主窗体类型的实例化。

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

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

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

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