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

C#泛型-根据对象类型查找正确的具体类,c#,generics,reflection,inheritance,interface,C#,Generics,Reflection,Inheritance,Interface,我的域模型中有几个新闻类型,它们是NewsItem的子类,如下所示(简化): 以下是NewsItem的几个子类: public class NewsItemJoiner : NewsItem { public virtual Account AccountJoined { get; set; } } public class NewsItemStatus : NewsItem { public virtual string Status { get; set; } } 在我的

我的域模型中有几个新闻类型,它们是NewsItem的子类,如下所示(简化):

以下是NewsItem的几个子类:

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

public class NewsItemStatus : NewsItem
{
    public virtual string Status { get; set; }
}
在我的MVC应用程序中,我想返回一个Newsitem的集合,其中可能包含许多不同的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);
    }
}
公共接口iRender,其中T:NewsItem
{
字符串渲染(T项);
}
公共类联合招标人:iEnder
{
公共字符串呈现(NewsItemJoiner项)
{
返回String.Format(“{0}刚刚加入我们的音乐网络。”,item.AccountJoined.ArtistName);
}
}
公共类StatusUpdaterEnder:iEnder
{
公共字符串呈现(NewsItemStatus项)
{
返回String.Format(“{0}”,item.Status);
}
}

我需要根据新闻项的类型以某种方式调用正确的类呈现函数。

您可以制作一个字典,将新闻项的类型用作键,将呈现函数用作值。或者,您可以维护具有渲染函数的所有类的列表,或者仅维护所有渲染函数的列表,并使用反射来确定应该使用哪个方法。然而,在我看来,代替做任何这些,你应该考虑重新设计你的应用程序,以便NeXITEM抽象类本身有一个虚拟的渲染函数。这将大大简化您的任务


编辑:以前认为NewsItem是一个接口。

您可以制作一个字典,使用NewsItem的类型作为键,使用Render函数作为值。或者,您可以维护具有渲染函数的所有类的列表,或者仅维护所有渲染函数的列表,并使用反射来确定应该使用哪个方法。然而,在我看来,代替做任何这些,你应该考虑重新设计你的应用程序,以便NeXITEM抽象类本身有一个虚拟的渲染函数。这将大大简化您的任务


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

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

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); 
    } 
}
公共抽象类RenderableNewsItem:NewsItem { 抽象公共字符串Render(); } 公共类NewsItemStatus:RenderableNewsItem { 公共虚拟字符串状态{get;set;} 公共字符串呈现() { 返回String.Format(“{0}”,this.Status); } }
对于虚拟函数来说,这似乎是一个相当明显的例子

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); 
    } 
}
公共抽象类RenderableNewsItem:NewsItem { 抽象公共字符串Render(); } 公共类NewsItemStatus:RenderableNewsItem { 公共虚拟字符串状态{get;set;} 公共字符串呈现() { 返回String.Format(“{0}”,this.Status); } }
一种可能性:启动时(即在与呈现代码相关的静态构造函数中),迭代程序集中的类,实例化并存储
IRenderer
字典,实现映射到它们呈现的类型的实例

(此建议假设渲染器对象是线程安全的,因为您可能会一次从多个请求线程调用
Render
方法。如果它们不是线程安全的,则需要将字典更改为
,并为每次使用实例化渲染器。)

例如:

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

请注意,
RenderUtil
类将在第一次使用时通过
TypeInitializationException
抛出一个
DuplicateKeyException
,如果给定类型有多个呈现器。

一种可能性:启动时(即在与呈现代码相关的静态构造函数中),迭代程序集中的类,实例化并存储
irender
字典-实现映射到它们呈现的类型的实例

(此建议假设渲染器对象是线程安全的,因为您可能会一次从多个请求线程调用
Render
方法。如果它们不是线程安全的,则需要将字典更改为
,并为每次使用实例化渲染器。)

例如:

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

请注意,
RenderUtil
类将在第一次使用时通过
TypeInitializationException
抛出一个
DuplicateKeyException
,如果给定类型有多个呈现器。

请考虑反转控制逻辑,并在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);
    }
}

编辑:
替代技术
从他人的意见中得出的观点重新分离了关注点。我不知道你是否因为其他原因被设置在iRender上,但是如果你没有,还有一种技术不需要使用反射。您可以改用访问者模式

首先声明NewsItemVisitor类:

public abstract class NewsItemVisitor
{
    public abstract void Visit(NewsItemJoiner joiner);
    public abstract void Visit(NewsItemStatus status);
}
接下来,向NewsItem添加一个virtual Accept()方法(在本例中,我将您的数据类型更改为string,而不是Account、Status等):

现在您可以创建一个具体的访问者,它是我们的渲染器:

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中提供一个virtual Render()方法。例如

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

编辑:
替代技术
从他人的意见中得出的观点重新分离了关注点。我不知道你是否因为其他原因被设置在iRender上,但是如果你没有,还有一种技术不需要使用反射。您可以改用访问者模式

首先声明NewsItemVisitor类:

public abstract class NewsItemVisitor
{
    public abstract void Visit(NewsItemJoiner joiner);
    public abstract void Visit(NewsItemStatus status);
}
接下来,向NewsItem添加一个virtual Accept()方法(对于本例,I
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);
<% foreach (var newsItem in Model.NewsItems) { %>
    <%= Html.RenderPartial(newsItem.PartialViewName, newsItem) %>
<% } >