Как привести объект неизвестного типа к его типу во время выполнения? - PullRequest
1 голос
/ 10 января 2012

Подобные темы, которые обращаются к этому, возможно, существуют на 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.ObjectCollection и List<User>, кроме логики if else? Извините за длинный кусок, просто нужно объяснить мое требование ..

ОБНОВЛЕНИЕ: Ответ StriplingWarrior обязательно решит мою проблему, но мне интересно, как бы объект был приведен к своему первоначальному типу, если бы я реализовал последнюю функцию. Возможно, через отражение или что-то. Я отмечу решение этого как ответ. Спасибо @StriplingWarrior еще раз.

ОБНОВЛЕНИЕ 2: Чтобы было понятнее, забудьте обо всем, что было опубликовано выше. Предположим, у меня есть функция, как показано ниже. Admonish() - это метод расширения, который я определил для объектов типа List<Naughty> и List<Obedient> отдельно. Как я смогу написать только одну Sanitize функцию и передать ей List<Naughty или List<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 Ответ

3 голосов
/ 10 января 2012

Поскольку 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, чтобы выполнить более или менее то, что вы пытаетесь сделать, но оно не будет работать с методами расширения:

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