Это может быть глупый вопрос, но мои поиски до сих пор не увенчались успехом, и я беспокоюсь, что у меня может быть слишком конкретный вариант использования, чтобы найти четкий ответ где-либо еще.
Я работаю над созданием компонента Blazor, который использует список привязок в качестве входного параметра. Это сделано специально для того, чтобы компонент мог обнаруживать изменения в списке вне компонента и реагировать соответствующим образом. Список реализует интерфейс для элементов списка таким образом:
[Parameter]
public BindingList<IPointOfInterest> PointsOfInterest { get => ...; set => ...; }
Интерфейс, тем временем, выглядит так:
public interface IPointOfInterest
{
public float[] position { get; set; }
}
Оттуда я создаю экземпляр как свойство родительского объекта, например:
var currentImage = new Image { DataUri = $"images/image-001.jpg", PointsOfInterest = new BindingList<IPointOfInterest>() };
А затем на основе выбранного в данный момент объекта я просто привязываю его к компоненту следующим образом:
<ImageViewer Image = "@currentImage"
PointsOfInterest = "@currentImage.PointsOfInterest">
</ImageViewer>
Моя цель здесь — сделать так, чтобы интерфейс IPointOfInterest
можно было каким-то образом расширить/унаследовать/преобразовать, чтобы потребитель компонента Blazor мог добавлять свои собственные свойства по желанию и использовать их в том же коде без необходимости поддерживать отдельные объекты. . Пример может быть примерно таким:
public class PointOfInterest : IPointOfInterest
{
public float[] position { get; set; }
public int id { get; set; }
public string name { get; set; }
public string description { get; set; }
public float entityId { get; set; }
}
К сожалению, хотя это кажется достаточно простой вещью, использование этого класса в Binding List в качестве входных данных для компонента просто не работает, потому что компонент ожидает интерфейс, и даже если класс реализует интерфейс, компилятор не может этого сделать. t конвертировать между ними.
Возможные решения, о которых я думал до сих пор, которые не сработали:
Мне действительно кажется, что я упускаю что-то фундаментальное в том, как dotnet относится к такого рода вещам, но я также чувствую, что пытаюсь сделать что-то довольно простое и не слишком возмутительное, и это не должно быть так сложно, как я. м найти его. Если я ошибаюсь, пожалуйста, дайте мне знать, и в любом случае я был бы признателен за любые предложения о том, как сделать то, что я делаю лучше и/или по-другому, чтобы это действительно работало.
Кажется, мне лучше показать это, чем пытаться описать, так что вот рабочий пример того, чего я пытаюсь достичь.
Дело в том, что средство просмотра изображений заботится только об определенных свойствах класса PointOfInterest
, в частности о свойстве position
. Все остальные свойства будут иметь значение только за пределами средства просмотра изображений.
Обратите внимание, что этот код компилируется и работает правильно только потому, что я использую класс вместо интерфейса. Если бы я использовал этот интерфейс, я бы получил ошибку преобразования, которая полностью помешала бы компиляции кода.
🤔 А знаете ли вы, что...
С C# легко создавать графические приложения с помощью Windows Forms и WPF.
Я не думаю, что у меня достаточно информации, чтобы полностью понять, но, возможно, вы можете попробовать следующее
<ImageViewer Image = "@currentImage"
PointsOfInterest = "@(currentImage.PointsOfInterest as BindingList<IPointOfInterest>)">
</ImageViewer>
Или подход с явным или неявным приведением тоже может быть хорошим.
Вы должны уметь использовать интерфейсы и дженерики. Вот демо:
Классы данных и интерфейс
public interface IPointOfInterest
{
public decimal Lat { get; }
public decimal Long { get; }
}
public class DataA : IPointOfInterest
{
public decimal Lat { get; set; }
public decimal Long { get; set; }
}
public class DataB : IPointOfInterest
{
public decimal Lat { get; set; }
public decimal Long { get; set; }
}
public class DataC
{
public decimal Lat { get; set; }
public decimal Long { get; set; }
}
Составная часть:
@typeparam TItem
<h3>PointComponent</h3>
@if (list is not null)
{
@foreach (var item in list)
{
<div class = "p-2 row">
<div class = "col-2">
Lat: @item.Lat
</div>
<div class = "col-2">
Long: @item.Long
</div>
</div>
}
}
else
{
<div class = "p2">No Data</div>
}
@code {
[Parameter] public IEnumerable<TItem>? DataList { get; set; }
private IEnumerable<IPointOfInterest>? list
{
get
{
if (DataList is not null && DataList is IEnumerable<IPointOfInterest>)
return new List<IPointOfInterest>(DataList!.Cast<IPointOfInterest>());
return null;
}
}
}
И демонстрационная страница:
@page "/"
<PageTitle>Index</PageTitle>
<PointComponent DataList=dataA />
<PointComponent DataList=dataB />
<PointComponent DataList=dataC />
@code {
public IEnumerable<DataA> dataA = new List<DataA>
{
new DataA {Lat = 20, Long=10 },
new DataA {Lat = 20, Long=10 }
};
public IEnumerable<DataB> dataB = new List<DataB>
{
new DataB {Lat = 20, Long=10 },
new DataB {Lat = 20, Long=10 }
};
public IEnumerable<DataC> dataC = new List<DataC>
{
new DataC {Lat = 20, Long=10 },
new DataC {Lat = 20, Long=10 }
};
}
Выяснил решение, используя ограничения универсального типа. В конечном счете, однако, у меня очень специфический вариант использования. Как показывает ответ Шона Кертиса, эта проблема обычно решается с помощью интерфейса для перечислимого типа, а не принудительного использования конкретного класса. Если вам это сойдет с рук, я рекомендую последовать его примеру.
Если вы просто пытаетесь понять, почему ваш объект List<MyClass>
не будет привязан к вашему параметру List<IMyClass>
, я бы порекомендовал вам проверить этот очень информативный и гораздо более полезный ответ.
Мы с коллегой проработали это вместе и нашли решение, включающее универсальные типы и ограничения типов. Это немного круговое движение, но оно работает именно так, как мне нужно. Вот рабочий пример.
Соответствующие части включали добавление соответствующим образом ограниченного параметра универсального типа в компонент средства просмотра изображений, например...
@typeparam TPointOfInterest where TPointOfInterest : IPointOfInterest
... и затем используя универсальный тип в соответствующем параметре компонента:
[Parameter]
public BindingList<TPointOfInterest> PointsOfInterest { get ; set ; }
При этом средство просмотра изображений примет список привязок данного типа класса, хотя вам все равно нужно указать, какой класс использовать для ссылки на универсальный тип:
<ImageViewer Image = "@currentImage"
PointsOfInterest = "@currentImage.PointsOfInterest"
TPointOfInterest = "PointOfInterest">
</ImageViewer>
@code
{
public Image currentImage = new Image()
{
DataUri = "https://www.longimageurl.com/boy/this/is/an/awfully/long/url/for/just/one/image.jpg",
PointsOfInterest = new BindingList<PointOfInterest>() {
new PointOfInterest() {
position = new float[] { 1, 2, 3 },
id=1,
name = "POI #1",
description = "FOO"
},
new PointOfInterest() {
position = new float[] { 4, 5, 6 },
id=1,
name = "POI #2",
description = "BAR"
},
}
};
}
Это выполняет все, что мне нужно:
BindingList
от родителя к дочернему (в моем случае это важно для обеспечения синхронизации списка между ними)Это было раздражающе трудно взломать, учитывая то, что я не думаю, было особенно диковинной целью. Я надеюсь, что это в конечном итоге избавит кого-то еще от изжоги.