В Visual Studio 2022 ASP.NET Web Application Project (.NET Framework) я, похоже, смог создать пользовательский элемент управления с помощью дженериков:
public class StronglyTypedListBox<T> : System.Web.UI.UserControl {
protected ListBox lst; // This line actually exists in the .ascx.designer.cs file.
// I list it here for brevity.
public GuardedList<T> Items {
/* GuardedList<T> derives from System.Collections.ObjectModel.Collection<T>.
lst.Items will be automatically populated and updated whenever Items are modified. */
get { ... }
set { ... }
}
/* Other constructs providing functionalities such as removing an item and moving an item up/down */
}
и разметка .ascx:
<%@ Control ...... Inherits = "MyProject.StronglyTypedListBox<T>" %>
<asp:ListBox id = "lst" runat = "server"></asp:ListBox>
<%-- Other markup providing functionalities such as removing an item and moving an item up/down --%>
Чтобы использовать этот «общий» пользовательский элемент управления на странице, я перетащил файл .ascx в разметку страницы (точно так же, как мы добавляем любой пользовательский элемент управления на страницу) и изменил его идентификатор на lst.
И я переместил следующую строку из файла .aspx.designer.cs
protected StronglyTypedListBox<T> lst;
в файл .aspx.cs и изменил его следующим образом:
protected StronglyTypedListBox<OneOfMyEntityType> lst;
Все вышеперечисленное, казалось, было в порядке в Visual Studio. Нет красных волнистых линий, и проект строится без сообщений об ошибках. Но при нажатии F5 на странице появляется исключение, в котором говорится, что ей не удается разобрать строку 1 .ascx, потому что не удалось загрузить тип MyProject.StronglyTypedListBox<T>.
Возможно ли то, чего я хочу достичь с помощью приведенных выше кодов? Если да, то что нужно исправить?
@AlbertD.Kallal Боюсь, вы что-то не поняли ... После компиляции/сборки ничего не меняется. Все шаги, которые я описал, - это время разработки в исходных кодах. .aspx.designer.cs является частью исходного кода, и в нем комментарии, которые генерирует Visual Studio, говорят, что вы можете переместить автоматически сгенерированное объявление поля (которое представляет дочерний элемент управления) из .aspx.designer.cs в .aspx.cs и изменить это если то, что сгенерировано VS, не соответствует вашим потребностям.
Хорошо, достаточно честно - просто я не хочу, чтобы такие изменения перезаписывались - но, в конце концов, я все равно не помогаю вашему сообщению - мои извинения. Возможно, здесь сработает разрешение параметра или метода, который принимает тип объекта.
@AlbertD.Kallal Я думал об этом, чтобы - отказаться от универсального пользовательского элемента управления, вместо этого добавив к нему свойство Type для получения информации о типе... Но, как показывают коды в моем сообщении, пользовательский элемент управления использует GuardedList<T> . Я не знаю, как преобразовать объект Type в параметр универсального типа, чтобы GuardedList<T> мог его использовать...





Как обсуждалось в комментариях, выйдите из дженериков, потому что aspnet_compiler.exe просто не поддерживает его. Вместо этого имейте свойство Type и используйте отражение, которое является ключом к решению.
Например, ниже показан пользовательский элемент управления, содержащий ListBox (с именем lst) с его ListItem, сопоставленными с коллекцией (определяемой как свойство Items) определенного типа (определяемой как свойство ItemType). То есть ListItems ListBox автоматически заполняются/добавляются/удаляются в соответствии с Items, а после обратной передачи Items могут быть перестроены из ListItems.
public partial class MappedListBox : System.Web.UI.UserControl {
public Type ItemType {
get => this._ItemType;
set {
if (this._ItemType != value && this._Items is object)
throw new InvalidOperationException();
this._ItemType = value;
}
}
public Func<object, ListItem> ItemToListItem { get; set; }
public Func<ListItem, object> ListItemToItem { get; set; }
public IList Items {
set {
this.Items.Clear();
if (value is object) foreach(var item in value) this.Items.Add(item);
}
get {
if (this._Items is null) {
if (this.ItemType is null) throw new ArgumentNullException(nameof(ItemType));
//Recover entity objects from ListBox.Items as an IEnumerable<ItemType>
var initialItems =
typeof(Enumerable)
.GetMethod(nameof(Enumerable.Cast))
.MakeGenericMethod(this.ItemType)
.Invoke(
null
, new[] {
this.lst.Items.Cast<ListItem>().Select(i => this.ListItemToItem(i))
}
);
//Rebuild this._Items as a GuardedList<ItemType>, filling it with initialItems,
//and specify that addition/removal inside this list should automatically add/remove corresponding ListItems.
//(This capability comes from System.Collections.ObjectModel.Collection<T>, which GuardedList<T> inherits.)
var typeOfGuardedListOfItemType =
typeof(GuardedList<>).MakeGenericType(this.ItemType);
var guardedList =
Activator.CreateInstance(
typeOfGuardedListOfItemType
, initialItems
, (Action<int, object>)(
(index, item) =>
this.lst.Items.Insert(index, this.ItemToListItem(item))
)
, (Action<int>)(index => this.lst.Items.RemoveAt(index))
, (Action)(() => this.lst.Items.Clear())
, (Action<int, object>)(
(index, item) => {
var listItem = this.ItemToListItem(item);
this.lst.Items[index].Value = listItem.Value;
this.lst.Items[index].Text = listItem.Text;
}
)
);
this._Items = (IList)guardedList;
}
return this._Items;
}
}
/* Constructs providing other functionalities such as moving an item up/down and so on */
Type _ItemType;
IList _Items;
}
И его можно использовать на странице или другом пользовательском элементе управления следующим образом:
public partial class EmployeeSelection : System.Web.UI.UserControl {
// The MappedListBox instance is named lstSelected
protected override void OnInit(EventArgs e) {
this.lstSelected.ItemType = typeof(Employee);
this.lstSelected.ItemToListItem = item => {
var employee = (Employee)item;
return new ListItem(employee.Name, employee.ID);
};
this.lstSelected.ListItemToItem = listItem => DB.Employees.Find(listItem.Value);
this.EmployeeTreeView.SelectedNodeChanged += (s, ev) =>
this.SelectedEmployees.Add(getChosenEmployeeFromTreeView());
base.OnInit(e);
}
internal IList<Employee> SelectedEmployees {
get => (IList<Employee>)this.lstSelected.Items;
set => this.lstSelected.Items = value?.ToArray();
}
}
Я не понимаю, как ЛЮБОЙ подход к разработке программного обеспечения предполагает, что после того, как вы скомпилируете код, вы ЗАТЕМ измените несколько байтов в скомпилированном выводе. И я не понимаю, как ЛЮБАЯ практическая разработка программного обеспечения может предложить и принять идею о том, что после того, как вы создадите свой проект, вы должны изменить файл desinger.cs. Действительно, любое решение, основанное на такой парадигме, обречено — на 100% обречено. Что еще хуже, перетаскивание этого элемента управления на любую веб-страницу, конечно, ТОГДА сгенерирует новый фрагмент кода дизайнера для данной страницы. На самом деле, независимо от того, какое решение вы примете, эта идея никуда не годится.