C#泛型 - 根据对象类型找到正确的具体类

时间:2010-02-16 17:01:49

标签: c# generics reflection inheritance interface

我的域模型有几个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子类。我现在需要做的是遍历每个新闻项并从相关类中为该特定类型的NewsItem调用Render函数...代码可能会更容易解释它:

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 个答案:

答案 0 :(得分:3)

对于虚拟函数来说,这似乎是一个相当明显的例子......

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

答案 1 :(得分:2)

您可以创建一个使用NewsItem类型作为键的字典,并将Render函数用作值。或者,您可以使用Render函数或仅使用所有Render函数维护所有类的列表,并使用Reflection来确定应使用哪种方法。但是,在我看来,您应该考虑重新设计应用程序,以便NewsItem抽象类本身具有虚拟渲染功能,而不是执行任何操作。这将大大简化您的任务。

编辑:以前认为NewsItem是一个界面。

答案 2 :(得分:1)

一种可能性:在启动时(即在与渲染代码相关的静态构造函数中),遍历程序集中的类并实例化并存储Dictionary<Type, object> IRenderer<T> - 实现映射到他们渲染的类型。

(这个建议假设渲染器对象是线程安全的,因为你可能最终一次从多个请求线程调用Render方法。如果它们不是线程安全的,那么你' d需要将字典更改为<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

答案 3 :(得分:1)

我要做的是使用多个部分视图来渲染不同的NewsItem子类。然后,我会在子类和部分视图名称之间进行某种映射。这有两种方法:

  1. NewsItem可以有一个虚拟字符串属性/方法,它返回与之关联的局部视图的名称。我只推荐这个,如果NewsItem是一个专门用于传递给视图的模型类,而不是它是一个ORM类或类似的。
  2. 在包含新闻项列表的模型中,您可以拥有映射属性(例如Dictionary<Type, string>)。
  3. 完成此设置后,您的视图可能如下所示:

    <% foreach (var newsItem in Model.NewsItems) { %>
        <%= Html.RenderPartial(newsItem.PartialViewName, newsItem) %>
    <% } >
    

答案 4 :(得分:0)

考虑反转控制逻辑并在NewsItem中提供虚拟Render()方法。 E.g。

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

编辑:
替代技术
关注其他人的意见重新分离关注点。我不知道你是否已经设置了IRenderer&lt; T&gt;由于其他原因,但如果你不是,那么还有另一种技术不需要使用反射。您可以改为使用访客模式。

首先声明一个NewsItemVisitor类:

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

接下来,向NewsItem添加一个虚拟的Accept()方法(对于这个例子,我已经将数据类型更改为字符串而不是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

答案 5 :(得分:0)

我刚刚做了类似的事情,但我没有方便的代码。它使用了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);