将通用<tobject>类传递给表单

时间:2017-08-02 13:34:07

标签: c# forms generic-programming tobject

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

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

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

然后以这种方式构建:

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

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

有没有人知道这是否可能,如果可能的话?

我已经使用Form构造函数进行了一些实验,但无济于事。

非常感谢, 戴夫

更新:澄清我想要取得的成果是什么

enter image description here

更新:今年4月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; }
}

5 个答案:

答案 0 :(得分:10)

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

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

结果是:

enter image description here

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

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

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

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

MessageBox.Show(GenericType as string);

结果:

enter image description here

然后让我们做作业。以下代码应该实现您想要做的。

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时),上述代码将会爆炸 - 运行时异常(因为{{1} }没有无参数构造函数):

enter image description here

为了保护您的代码,我们可以在可能的类中继承接口。这将导致编译时异常,这是更可取的:

enter image description here

代码如下。

string

更新

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

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

这应该会引发异常,因为你刚刚删除了隐式无参数构造函数。当然,如果你知道自己在做什么,这很难发生,但是我们可以通过{&#39}使用public class MyObj : IAllowedParamType { public int Id { get; set; } public MyObj(int id) { Id = id; } } 来禁止这种情况。类型过滤 - 同时也摆脱了new()的东西。

整个代码:

Activator.CreateInstance

答案 1 :(得分:3)

我认为您可以向FormController添加新类型参数:

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

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

答案 2 :(得分:2)

据我所知,您需要Form<T>MainForm中执行某些操作,并使用MainForm使用FormController作为所有人的管理员您的表单,将通用类型信息转发给您的Form<T>。此外,Form<T>类的实例化对象应该从DatabaseController<T>请求FormController类的实例。

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

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<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的实例:

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

  2. 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以忠实于单一责任原则。 e.g:

    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>。这使您可以更好地管理控制器的生命周期并在应用程序中形成对象。

答案 3 :(得分:1)

嗯,我不打算详细介绍这些细节,只能填写一些蓝图。 在这种情况下,我使用Unity构造函数注入与通用工厂的组合来处理给定主窗体类型的实例化。

不是那么错综复杂,请看看Unity文档 Dependency Injection with Unity

从所有DI容器中挑选Unity的原因是它是Microsoft自身企业库的一部分,现在继续作为Nugget形式的独立库生存。我的一个朋友最近也将Unity移植到.Net核心。简单地说,它可以放下最精致的容器。

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

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

答案 4 :(得分:0)

尝试工厂方法。

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