Передача универсального класса <TObject> в форму
Я не могу найти ответ на этот вопрос с помощью поиска, поэтому здесь идет....
Я знаю, что могу передавать объекты Class в целом другим классам, используя этот тип кода:
public class ClsGeneric<TObject> where TObject : class
{
public TObject GenericType { get; set; }
}
Тогда построим таким образом:
ClsGeneric<MyType> someName = new ClsGeneric<MyType>()
Однако у меня есть приложение, которое требует, чтобы я открыл форму и каким-то образом передал универсальный тип для использования в этой форме. Я пытаюсь использовать эту форму для разных типов классов.
Кто-нибудь знает, возможно ли это, и если да, то как?
Я немного поэкспериментировал с конструктором Form, но безрезультатно.
Большое спасибо заранее, Дейв
ОБНОВЛЕНО: Разъяснение того, какого результата я пытаюсь достичь
ОБНОВЛЕНО: 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 ответов
Я думаю, что вы можете добавить новый аргумент типа FormController
:
public class FormController<TParent, TChild>
where TParent : class
where TChild : class
{
...
public void showSubForm(string className)
{
IFormInterface f2 = new FormInterface<TChild>();
f2.ShowDialog();
}
}
Возможно, вы пробовали это, но вы можете создать собственный класс:
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();
Результат:
Идя дальше, вы можете создать экземпляр типа TObject
изнутри GenericForm
класс, используя Activator
учебный класс:
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));
}
}
Однако, глядя на ваш текущий пример, у вас есть два класса, Parent
а также Child
, Если я правильно понимаю, это единственные возможности, чтобы быть типом 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;
}
}
И это все равно должно вызвать исключение, потому что вы просто удалили неявный конструктор без параметров. Конечно, если вы знаете, что делаете, это трудно сделать, однако мы можем запретить это, используя 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();
}
}
Итак, как я понимаю, вы хотите Form<T>
открыть на какое-то действие в MainForm
, с вашим MainForm
используя FormController
как менеджер всех ваших форм, передавая информацию об общем типе вашему Form<T>
, Кроме того, экземпляр объекта вашего Form<T>
класс должен запросить экземпляр DatabaseController<T>
класс от вашего FormController
,
Если это так, следующая попытка может сработать:
MainForm
получает ссылку на FormController
экземпляр при инициализации конструктора или имеет другой способ взаимодействия с FormController
например, CommonService
о которых оба знают и т. д.
Это позволяет MainForm
вызвать универсальный метод FormController
создать и показать новый объект формы:
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
не должен быть универсальным, в противном случае вам понадобится новый экземпляр FormController для каждого T. Это просто необходимо предоставить универсальный метод.
Ваш
Form<T>
получает экземпляр DatabaseController от FormController после инициализации конструктора:void FormController.CreateForm () {Форма формы = новая форма (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 для регистрации и получения экземпляров определенного типа во время выполнения. каждый Form<T>
получает экземпляр контейнера IoC во время выполнения и запрашивает соответствующий DatabaseController<T>
, Это позволяет вам лучше управлять временем жизни вашего контроллера и формировать объекты в приложении.
Ну, я не буду вдаваться в подробности здесь и хватит только на некоторые чертежи. В этом сценарии я бы использовал комбинацию инъекций конструктора Unity с универсальной фабрикой для обработки экземпляров данного типа в главной форме.
Это не так сложно, взгляните на документацию Unity в Dependency Injection with Unity
Причина выбора Unity из всех DI-контейнеров заключается в том, что он был частью Enterprise Library от самой Microsoft и теперь продолжает жить как независимая библиотека в форме Nugget. Мой друг недавно также перенес Unity на ядро .Net. Проще говоря, он вручает самый сложный из доступных контейнеров.
Что касается фабрики, я считаю, что это необходимо, потому что вы не хотите создавать конкретный поиск для обработки всех возможных типов, так что это явно должна быть универсальная фабрика. Я бы посоветовал вам сделать вашу фабрику синглтоном и поместить ее в совершенно другой проект, тем самым отделив ваш проект пользовательского интерфейса от моделей, и обе стороны будут взаимодействовать через этот мост DI. Вы даже можете сделать шаг вперед и обработать типы моделей, используя отражение сборки. извините за слишком общий, но я действительно не знаю, насколько вы знакомы с этими образцами. Это действительно стоит потратить некоторое время и использовать эти шаблоны. по моему скромному мнению, от этих маневров не уйти, если вы хотите действительно масштабируемое программное обеспечение.
Вы можете связаться со мной в частном порядке, если вы ищете советы по реализации любой из вышеупомянутых стратегий.
Попробуйте Фабричный метод.
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();