C # Generics - найти правильный конкретный класс в зависимости от типа объекта - PullRequest
1 голос
/ 16 февраля 2010

У меня есть модель моего домена с несколькими типами NewsType, которые являются подклассами NewsItem, как показано ниже (упрощенно):

public abstract class NewsItem : Entity
{
    public virtual Account Account { get; set; }
    public virtual DateTime DateTime { get; set; }
}

Вот пара подклассов NewsItem:

public class NewsItemJoiner : NewsItem
{
    public virtual Account AccountJoined { get; set; }
}

public class NewsItemStatus : NewsItem
{
    public virtual string Status { get; set; }
}

В моем приложении MVC я хочу вернуть коллекцию Newsitem, которая может содержать множество различных подклассов NewsItem. Что мне теперь нужно сделать, так это циклически проходить по каждому элементу новостей и вызывать функцию Render из соответствующего класса для этого конкретного типа NewsItem ... код может объяснить это немного проще:

public interface IRenderer<T> where T : NewsItem
{
    string Render(T item);
}

public class JoinedRenderer : IRenderer<NewsItemJoiner>
{
    public string Render(NewsItemJoiner item)
    {
        return String.Format("{0} has just joined our music network.", item.AccountJoined.ArtistName);
    }
}

public class StatusUpdateRenderer : IRenderer<NewsItemStatus>
{
    public string Render(NewsItemStatus item)
    {
        return String.Format("<span class='statusupdate'>{0}<span>", item.Status);
    }
}

Мне нужно как-то вызвать правильные классы функции Render в зависимости от типа NewsItem.

Ответы [ 6 ]

3 голосов
/ 16 февраля 2010

Это кажется довольно очевидным случаем для виртуальной функции .....

public abstract class RenderableNewsItem : NewsItem
{
    abstract public string Render();
}

public class NewsItemStatus : RenderableNewsItem 
{ 
    public virtual string Status { get; set; } 
    public string Render() 
    { 
        return String.Format("<span class='statusupdate'>{0}<span>", this.Status); 
    } 
}
2 голосов
/ 16 февраля 2010

Вы можете создать словарь, который будет использовать тип NewsItem в качестве ключа и функцию Render для использования в качестве значения. Или вы можете сохранить список всех классов с функциями Render или просто всех функций Render и использовать Reflection, чтобы определить, какой метод следует использовать. Однако мне кажется, что вместо того, чтобы что-либо из этого делать, вам следует подумать о редизайне вашего приложения, чтобы сам абстрактный класс NewsItem имел виртуальную функцию Render. Это значительно упростит вашу задачу.

Редактировать: ранее думал, что NewsItem был интерфейсом.

1 голос
/ 16 февраля 2010

Вместо этого я бы использовал несколько частичных представлений для рендеринга различных NewsItem подклассов. Тогда у меня было бы какое-то отображение между подклассами и частичными именами представлений. Вот два способа сделать это:

  1. NewsItem может иметь свойство / метод виртуальной строки, который возвращает имя частичного представления, связанного с ним. Я бы порекомендовал это, только если NewsItem - это класс модели, специально предназначенный для передачи в представления, а не класс ORM или аналогичный.
  2. В модели, содержащей список новостей, у вас может быть свойство отображения (например, Dictionary<Type, string>).

Как только вы это настроите, ваш вид может выглядеть примерно так:

<% foreach (var newsItem in Model.NewsItems) { %>
    <%= Html.RenderPartial(newsItem.PartialViewName, newsItem) %>
<% } >
1 голос
/ 16 февраля 2010

Одна возможность: при запуске (т. Е. В статическом конструкторе, связанном с вашим кодом рендеринга), перебирать классы в вашей сборке и создавать и сохранять Dictionary<Type, object> из IRenderer<T> -элементарных экземпляров, сопоставленных с типом, который они представляют ,

(В этом предложении предполагается, что объекты рендерера являются поточно-ориентированными, так как вы можете в конечном итоге вызвать метод Render из более чем одного потока запроса одновременно. Если они не являются потоко-безопасными, вам потребуется изменить словарь на <Type, Type> и создать рендер для каждого использования.)

Например:

public class RenderUtil
{
    static Dictionary<Type, object> s_renderers;

    static RenderUtil()
    {
        s_renderers = new Dictionary<Type, object>();

        foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
        {
            var renderInterface = type.GetInterfaces().FirstOrDefault(
                i => i.IsGenericType && 
                     i.GetGenericTypeDefinition() == typeof(IRenderer<>));

            if (renderInterface != null)
            {
                s_renderers.Add(
                    renderInterface.GetGenericArguments()[0],
                    Activator.CreateInstance(type));
            }
        }
    }

    public static string Render<T>(T item)
    {
        IRenderer<T> renderer = null;
        try
        {
            // no need to synchronize readonly access
            renderer = (IRenderer<T>)s_renderers[item.GetType()];
        }
        catch
        {
            throw new ArgumentException("No renderer for type " + item.GetType().Name);
        }

        return renderer.Render(item);
    }
}

Использование:

var newsItem = new NewsItemStatus();

// in your example code, ends up calling StatusUpdateRenderer.Render:
var rendered = RenderUtil.Render(newsItem);

Обратите внимание, что класс RenderUtil при первом использовании выдает DuplicateKeyException через TypeInitializationException, если для данного типа имеется более одного средства визуализации.

0 голосов
/ 16 февраля 2010

Я только что сделал что-то подобное, но у меня нет кода под рукой. Он использовал Reflection и выглядел следующим образом:

List<GenericContainer> gcList = new List<GenericContainer>();
// GenericContainer can be a Jug, Bottle, Barrel, or just a GenericContainer type
// [..fill it..]
GenericContainer gc = gcList[i];
Object returnvalue = gc.GetType()
                     .GetMethod("Pour", BindingFlags.Instance).Invoke(gc, null);
0 голосов
/ 16 февраля 2010

Рассмотрите возможность инвертирования логики управления и предоставления виртуального метода Render () в NewsItem. Э.Г.

abstract class NewsItem {
    // ...
    public virtual string Render() { return string.Empty; }
}

Тогда ваш подкласс может реализовать по желанию:

public class NewsItemJoiner : NewsItem
{
    // ...
    public override string Render() {
        return String.Format("{0} has just joined our music network.", this.AccountJoined.ArtistName);
    }
}

Edit:
Альтернативная техника
Точка, взятая о комментариях от других повторного разделения проблем. Я не знаю, установлен ли вы на IRenderer по другим причинам, но если нет, есть еще один метод, который не требует использования отражения. Вместо этого вы можете использовать шаблон Visitor.

Сначала вы объявляете класс NewsItemVisitor:

public abstract class NewsItemVisitor
{
    public abstract void Visit(NewsItemJoiner joiner);
    public abstract void Visit(NewsItemStatus status);
}

Затем добавьте виртуальный метод Accept () в NewsItem (для этого примера я изменил ваши типы данных на строковые вместо Account, Status и т. Д.):

public abstract class NewsItem
{
    public virtual string Account { get; set; }
    public virtual DateTime DateTime { get; set; }
    public abstract void Accept(NewsItemVisitor visitor);
}

public class NewsItemJoiner : NewsItem
{
    public virtual string AccountJoined { get; set; }
    public override void Accept(NewsItemVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class NewsItemStatus : NewsItem
{
    public virtual string Status { get; set; }
    public override void Accept(NewsItemVisitor visitor)
    {
        visitor.Visit(this);
    }
}

Теперь вы можете создать конкретного посетителя, который является нашим средством визуализации:

public class NewsItemListRenderer : NewsItemVisitor
{
    private readonly List<NewsItem> itemList;
    private string renderedList = string.Empty;

    public NewsItemListRenderer(List<NewsItem> itemList)
    {
        this.itemList = itemList;
    }

    public string Render()
    {
        foreach (var item in itemList)
        {
            item.Accept(this);
        }

        return renderedList;
    }

    public override void Visit(NewsItemJoiner joiner)
    {
        renderedList += "joiner: " + joiner.AccountJoined + Environment.NewLine;
    }

    public override void Visit(NewsItemStatus status)
    {
        renderedList += "status: " + status.Status + Environment.NewLine;
    }
}

Пример кода для отображения списка экземпляров NewsItem:

List<NewsItem> itemList = new List<NewsItem>();
itemList.Add(new NewsItemJoiner { AccountJoined = "fred" });
itemList.Add(new NewsItemJoiner { AccountJoined = "pete" });
itemList.Add(new NewsItemStatus { Status = "active" });
itemList.Add(new NewsItemJoiner { AccountJoined = "jim" });
itemList.Add(new NewsItemStatus { Status = "inactive" });

NewsItemListRenderer renderer = new NewsItemListRenderer(itemList);
Console.WriteLine(renderer.Render());

Запуск этого дает следующий вывод:

joiner: fred
joiner: pete
status: active
joiner: jim
status: inactive
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...