如何在运行时将类型未知的对象强制转换为类型?

时间:2012-01-10 18:53:18

标签: c# c#-4.0 types casting runtime

解决这个问题的类似线程可能就在SO上,但我不理解它们:(

我有这样的场景:

internal static void AddToListBox(ListBox lb, User usr)
{
    if (CanAddUser(lb, usr))
        lb.Items.Add(usr);
}

static bool CanAddUser(ListBox lb, User usr)
{
    foreach (User u in lb.Items)
    {
        if (u.Id == usr.Id)
            return false;
    }
    return true;
}

现在我有另一个完全类似的情况:

internal static void AddToList(List<User> lstUser, User usr)
{
    if (CanAddUser(lstUser, usr))
        lstUser.Add(usr);
}

static bool CanAddUser(List<User> lstUser, User usr)
{
    foreach (User u in lstUser)
    {
        if (u.Id == usr.Id)
            return false;
    }
    return true;
}

基本上两组都做同样的事情。我很好奇,如果我可以使它成为一个函数,但这只是它的工作原理,这使代码更加冗长:

internal static void AddToList(IEnumerable enumerable, User usr)
{
    if (enumerable is ListBox.ObjectCollection)
    {
        if (CanAddUser(enumerable, usr))
            ((ListBox.ObjectCollection)enumerable).Add(usr);
    }
    else if (enumerable is List<User>)
    {
        if (CanAddUser(enumerable, usr))
            ((List<User>)enumerable).Add(usr);
    }
}

internal static bool CanAddUser(IEnumerable enumerable, User usr)
{
    foreach (User u in enumerable)
    {
        if (u.Id == usr.Id)
            return false;
    }
    return true;
}

正如我所说的那样有效,但if else块使它看起来很丑陋并且杀死了缩短代码的想法。我可以做这样的事情:

internal static void AddToList(IEnumerable enumerable, User usr)
{
    if (CanAddUser(enumerable, usr))
        ((whatever the type is passed, just find it yourself)enumerable).Add(usr);
}

...

我试过

internal static void AddToList(IEnumerable enumerable, User usr)
{
    if (CanAddUser(enumerable, usr))
        ((enumerable.GetType()) / (typeof(enumerable))enumerable).Add(usr);
    //both which wont compile.
}

那怎么办呢?是否无法将IEnumerable转换为未知类型?或者解决方案是否太乱了我应该放弃计划?如果我知道传递的IEnumerable只有ListBox.ObjectCollectionList<User>而不是if else逻辑,那么可以做任何事情吗?很抱歉这篇冗长的文章只是需要解释我的要求..

UPDATE: StriplingWarrior的答案必然会解决我的问题,但我想知道如果我要实现最后一个函数,对象是如何转换为其原始类型的。可能通过反思或其他东西。我会将解决方案标记为答案。再次感谢@StriplingWarrior。

更新2: 为了更清楚,忘记上面发布的所有内容。假设我有类似下面的功能。 Admonish()是我在List<Naughty>List<Obedient>类型的对象上分别定义的扩展方法。我如何才能只编写一个Sanitize函数并将List<NaughtyList<Obedient传递给它?像这样:

internal static void Sanitize(IEnumerable enumerable)
{
    enumerable.Admonish();
}

...

我知道我可以这样做:

internal static void Sanitize(IEnumerable enumerable)
{
    if (enumarable is List<Naughty>)
        ((List<Naughty>)enumerable).Admonish();
    if (enumarable is List<Obedient>)
        ((List<Obedient>)enumerable).Admonish();
}

我将如何一次性完成这项工作:

internal static void Sanitize(IEnumerable enumerable)
{
    ((Determine type here, but how??)enumerable).Admonish();
}

希望问题很清楚。我不是在寻找一个特殊的案例解决方案,而是在运行时获取类型的一般解决方案,以便可以将对象转换为其类型 !!

1 个答案:

答案 0 :(得分:3)

由于ListBox.ObjectCollection实现了IList接口,因此您应该能够在两种情况下都使用该接口。

internal static void AddToList(IList list, User usr)
{
    if (!list.Cast<User>().Any(u => u.Id == user.Id))
        list.Add(usr);
}

这对IEnumerable不起作用的唯一原因是因为该界面没有Add方法。

对更新2的响应

这个问题比初看起来要复杂得多,答案取决于你的Admonish方法的作用。它只是迭代列表并在列表的每个成员上执行特定的操作吗?

public static void Admonish(this IEnumerable<Naughty> followers)
{
    foreach(var follower in followers) { follower.Preach(); }
}

public static void Admonish(this IEnumerable<Obedient> followers)
{
    foreach(var follower in followers) { follower.Preach(); }
}

如果是这种情况,你可以让Naughty和Obedient实现一个特定的接口:

public interface IFollower
{
    void Preach();
}

public class Naughty : IFollower {...}
public class Obedient : IFollower {...}

public static void Admonish(this IEnumerable<IFollower> followers)
{
    foreach(var follower in followers) { follower.Preach(); }
}

internal static void Sanitize(IEnumerable enumerable)
{
    enumerable.Cast<IFollower>().Admonish();
}

另一方面,如果Admonish根据你正在看的类型做了不同的事情,那不是那么简单:

public static void Admonish(this IEnumerable<Naughty> followers)
{
    foreach(var follower in followers) { follower.Chastise(); }
}

public static void Admonish(this IEnumerable<Obedient> followers)
{
    foreach(var follower in followers) { follower.Encourage(); }
}

在这种情况下,自动调用正确的Admonish方法的唯一方法是使用反射来查找和调用它。就.NET框架而言,没有理由相信这两个Admonish方法有任何关联,所以你不能只假装它们是相同的方法。

如果您愿意使用非静态方法制作Admonish,则可以使用dynamic关键字来完成或多或少的尝试,但它不适用于扩展方法:< / p>

public class Admonisher
{
    public void Admonish(IEnumerable<Naughty> followers)
    {
        foreach(var follower in followers) { follower.Chastise(); }
    }

    public void Admonish(IEnumerable<Obedient> followers)
    {
        foreach(var follower in followers) { follower.Encourage(); }
    }
}

internal static void Sanitize(dynamic enumerable)
{
    dynamic admonisher = new Admonisher();
    admonisher.Admonish(enumerable);
}

但即使这种方法只有在您传入的可枚举由通用IEnumerable<Naughty>IEnumerable<Obedient>支持的情况下才有效。如果你传递ListBox.ObjectCollection,即使动态运行时框架也无法确定要调用的Admonish方法。

通过反射找到并调用正确的方法

首先让我说反射通常不是这类问题的最佳解决方案。反思很慢,它否定了编译编程语言的大部分优点。但是如果你认为这是要走的路,那么你就是这样做的。假设您的Admonish方法签名如下所示:

public static void Admonish(this IEnumerable<Naughty> followers)

...并假设您有一个实际实现类型实现IEnumerable<Naughty>的列表:

IList list = new List<Naughty>(); // List<Naughty> implements IEnumerable<Naughty>

...你可以找到并调用这样的方法:

var utilClass = typeof(Utility); // the class with your admonish methods
var admonishMethod = 
    (from m in utilClass.GetMethods()
    where m.IsStatic && m.Name == "Admonish"
    let parameter = m.GetParameters()[0]
    where parameter.ParameterType.IsAssignableFrom(list.GetType())
    select m).Single();
admonishMethod.Invoke(null, new[]{list});