Приведение переменной с использованием переменной типа

281

В C # я могу привести переменную типа объекта к переменной типа T, где T определена в переменной типа?

theringostarrs
источник
12
Не совсем по теме, но вы, кажется, достаточно нечетко понимаете, что такое «приведение» означает, что может быть хорошей идеей точно понять, какова цель и семантика оператора приведения. Вот хорошее начало: blogs.msdn.com/ericlippert/archive/2009/03/19/…
Эрик Липперт,
2
Я думал, что что-то придумал. Если у вас есть Typeпеременная, вы можете использовать отражение, чтобы создать экземпляр этого типа. И затем вы можете использовать универсальный метод для возврата нужного вам типа, выводя его из параметра этого типа. К сожалению, любой метод отражения, который создает экземпляр типа, будет иметь возвращаемый тип object, поэтому ваш обобщенный CastByExampleметод также будет использовать object. Так что на самом деле нет никакого способа сделать это, и даже если бы был, что бы вы сделали с вновь созданным объектом? Вы не могли использовать его методы или что-то еще, потому что вы не знаете его тип.
Кайл Делани
@KyleDelaney Спасибо, я полностью согласен! Как я пытался объяснить в своем ответе, на самом деле не очень полезно приводить что- то к другой вещи, не определяя в какой-то момент тип, который вы фактически используете. Все дело в типах - проверка типов во время компиляции. Если вам просто нужно сделать вызовы на объекте, вы можете использовать objectили dynamic. Если вы хотите динамически загружать внешние модули, вы можете сделать так, чтобы классы имели общий интерфейс и приводили объект к этому. Если вы не контролируете сторонний код, создайте небольшие обертки и реализуйте интерфейс для этого.
Zyphrax

Ответы:

203

Вот пример приведения и преобразования:

using System;

public T CastObject<T>(object input) {   
    return (T) input;   
}

public T ConvertObject<T>(object input) {
    return (T) Convert.ChangeType(input, typeof(T));
}

Редактировать:

Некоторые люди в комментариях говорят, что этот ответ не отвечает на вопрос. Но линия (T) Convert.ChangeType(input, typeof(T))обеспечивает решение. Convert.ChangeTypeМетод пытается преобразовать любой объект типа при условии в качестве второго аргумента.

Например:

Type intType = typeof(Int32);
object value1 = 1000.1;

// Variable value2 is now an int with a value of 1000, the compiler 
// knows the exact type, it is safe to use and you will have autocomplete
int value2 = Convert.ChangeType(value1, intType);

// Variable value3 is now an int with a value of 1000, the compiler
// doesn't know the exact type so it will allow you to call any
// property or method on it, but will crash if it doesn't exist
dynamic value3 = Convert.ChangeType(value1, intType);

Я написал ответ с помощью дженериков, потому что я думаю, что это очень вероятный признак запаха кода, когда вы хотите преобразовать a somethingего a something elseбез обработки фактического типа. С надлежащими интерфейсами это не должно быть необходимо в 99,9% случаев. Возможно, есть несколько крайних случаев, когда речь заходит о том, что это может иметь смысл, но я бы рекомендовал избегать таких случаев.

Изменить 2:

Несколько дополнительных советов:

  • Постарайтесь, чтобы ваш код был максимально безопасным для типов. Если компилятор не знает тип, то он не может проверить правильность вашего кода, и такие вещи, как автозаполнение, не будут работать. Проще говоря: если вы не можете предсказать тип (ы) во время компиляции, то как компилятор сможет ?
  • Если классы, с которыми вы работаете, реализуют общий интерфейс , вы можете привести значение к этому интерфейсу. В противном случае рассмотрите возможность создания собственного интерфейса и попросите классы реализовать этот интерфейс.
  • Если вы работаете с внешними библиотеками, которые вы динамически импортируете, проверьте также общий интерфейс. В противном случае рассмотрите возможность создания небольших классов-оболочек, которые реализуют интерфейс.
  • Если вы хотите сделать вызов объекта, но не заботитесь о типе, сохраните значение в переменной objectили dynamic.
  • Обобщения могут быть отличным способом создания повторно используемого кода, который применяется ко многим различным типам, без необходимости точно знать, какие типы используются.
  • Если вы застряли, подумайте о другом подходе или рефакторинге кода. Ваш код действительно должен быть таким динамичным? Должен ли он учитывать какой-либо существующий тип?
Zyphrax
источник
145
Я не знаю, как это помогает ОП. У нее есть переменная типа, Tа не как таковая.
Nawfal
12
@nawfal, в основном линия Convert.ChangeType(input, typeof(T));дает решение. Вы можете легко заменить typeof(T)существующей переменной типа. Лучшим решением (если возможно) было бы предотвратить динамический тип все вместе.
Zyphrax
59
@ Zyphrax, нет, он все еще требует приведения, Tкоторый недоступен.
Nawfal
4
Я знаю, что результирующий объект действительно имеет тип, Tно все же вы получаете только objectв качестве ссылки. хм, я нашел вопрос интересным в предположении, что у OP есть только Typeпеременная и никакой другой информации. Как будто подпись метода Convert(object source, Type destination):) Тем не менее, я
понял, что ты
10
Как это решение этого вопроса? У меня та же проблема, и у меня нет универсального <T>. У меня есть только переменная типа.
Нури Тасдемир
114

Другие ответы не упоминают «динамический» тип. Таким образом, чтобы добавить еще один ответ, вы можете использовать «динамический» тип для хранения полученного объекта без необходимости приведения преобразованного объекта со статическим типом.

dynamic changedObj = Convert.ChangeType(obj, typeVar);
changedObj.Method();

Имейте в виду, что при использовании «динамического» компилятор обходит статическую проверку типов, которая может привести к возможным ошибкам во время выполнения, если вы не будете осторожны.

maulik13
источник
19
Это правильный ответ. Без динамического ключевого слова typeof (changeObj) является «объектом». С ключевым словом dynamic он работает безупречно, а typeof (changeObject) правильно отражает тот же тип, что и typeVar. Кроме того, вам не нужно (T) разыгрывать, что вы не можете делать, если не знаете тип.
rushinge
5
Я получил исключение «Объект должен реализовывать IConvertible» при использовании этого решения. Любая помощь?
Нури Тасдемир
@NuriTasdemir Трудно сказать, но я считаю, что преобразование, которое вы делаете, невозможно без IConvertible. Какие типы участвуют в вашем преобразовании?
maulik13
Хотя это работает, при использовании динамики наблюдается снижение производительности. Я бы рекомендовал не использовать их, если вы не работаете с другими средами выполнения (именно для этого и была разработана динамика).
Боло
19

Вот мой метод приведения объекта, но не к переменной общего типа, а к System.Typeдинамически:

Я создаю лямбда-выражение во время выполнения, используя System.Linq.Expressionsтип Func<object, object>, который распаковывает входные данные, выполняет желаемое преобразование типов, а затем дает результат в штучной упаковке. Новый нужен не только для всех типов, которые подвергаются приведениям, но также и для типов, которые подвергаются приведению (из-за шага распаковки). Создание этих выражений отнимает много времени из-за рефлексии, компиляции и динамического построения метода, который делается под капотом. К счастью, после создания выражения можно вызывать многократно и без больших накладных расходов, поэтому я кеширую каждое из них.

private static Func<object, object> MakeCastDelegate(Type from, Type to)
{
    var p = Expression.Parameter(typeof(object)); //do not inline
    return Expression.Lambda<Func<object, object>>(
        Expression.Convert(Expression.ConvertChecked(Expression.Convert(p, from), to), typeof(object)),
        p).Compile();
}

private static readonly Dictionary<Tuple<Type, Type>, Func<object, object>> CastCache
= new Dictionary<Tuple<Type, Type>, Func<object, object>>();

public static Func<object, object> GetCastDelegate(Type from, Type to)
{
    lock (CastCache)
    {
        var key = new Tuple<Type, Type>(from, to);
        Func<object, object> cast_delegate;
        if (!CastCache.TryGetValue(key, out cast_delegate))
        {
            cast_delegate = MakeCastDelegate(from, to);
            CastCache.Add(key, cast_delegate);
        }
        return cast_delegate;
    }
}

public static object Cast(Type t, object o)
{
    return GetCastDelegate(o.GetType(), t).Invoke(o);
}

Обратите внимание, что это не волшебство. Приведение не происходит в коде, как это происходит с dynamicключевым словом, конвертируются только базовые данные объекта. Во время компиляции нам все еще приходится тщательно выяснять, какого типа может быть наш объект, что делает это решение непрактичным. Я написал это как хак для вызова операторов преобразования, определенных произвольными типами, но, возможно, кто-нибудь найдет лучший вариант использования.

balage
источник
2
Требуетсяusing System.Linq.Expressions;
Аарон Д
4
Для меня это страдает той же проблемой, что и ответ Zyphrax. Я не могу вызвать методы для возвращенного объекта, потому что он все еще имеет тип "объект". Использую ли я его метод («a» ниже) или ваш метод («b» ниже), я получаю ту же ошибку при приведении (t) - «t» - переменная, но она используется как тип.Type t = typeof(MyGeneric<>).MakeGenericType(obj.OutputType); var a = (t)Convert.ChangeType(obj, t); var b = (t)Caster.Cast(t, obj);
muusbolla
@muusbolla Оригинальный ответ Zyphrax использует непатентованные значения и переменные типа, а не Type. Вы не можете использовать обычный синтаксис приведения, если все, что у вас есть, это объект Type. Если вы хотите иметь возможность использовать объект в качестве некоторого типа T во время компиляции, а не во время выполнения, вам нужно привести его с помощью переменной типа или просто фактического имени типа. Вы можете сделать первое, используя ответ Zaphrax.
Эшли
8

Если оставить в стороне упаковку и распаковку для простоты, то нет никакого конкретного действия во время выполнения, связанного с приведением в иерархию наследования. В основном это время компиляции. По сути, приведение говорит компилятору обрабатывать значение переменной как другой тип.

Что вы могли бы сделать после актерского состава? Вы не знаете тип, поэтому вы не сможете вызывать какие-либо методы для него. Там не было бы ничего особенного, что вы могли бы сделать. В частности, это может быть полезно, только если вы знаете возможные типы во время компиляции, приводите его вручную и обрабатывает каждый случай отдельно с помощью ifоператоров:

if (type == typeof(int)) {
    int x = (int)obj;
    DoSomethingWithInt(x);
} else if (type == typeof(string)) {
    string s = (string)obj;
    DoSomethingWithString(s);
} // ...
Мехрдад Афшари
источник
1
Не могли бы вы объяснить это более четко в отношении моего вопроса?
theringostarrs
Я пытаюсь объяснить, что ты сможешь сделать после этого? Вы ничего не можете сделать, поскольку компилятор C # требует статической типизации, чтобы иметь возможность делать полезные вещи с объектом
Мехрдад Афшари,
Ты прав. Я знаю ожидаемые типы двух переменных, которые отправляются в метод как тип 'объект'. Я хочу привести к ожидаемым типам, хранящимся в переменных, и добавить их в коллекцию. Гораздо проще перейти по типу и попытаться выполнить обычное приведение и отлов ошибок.
theringostarrs
4
Ваш ответ хороший, но чтобы быть придирчивым, отмечу, что приведение никогда не влияет на переменные . Никогда не разрешено приводить переменную к переменной другого типа; Типы переменных инвариантны в C #. Вы можете привести только значение, хранящееся в переменной, к другому типу.
Эрик Липперт
Меняет ли этот ответ введение динамической типизации в C # 4.0?
Даниэль Т.
6

Как ты мог это сделать? Вам нужна переменная или поле типа T, где вы можете хранить объект после приведения, но как вы можете иметь такую ​​переменную или поле, если вы знаете T только во время выполнения? Так что нет, это невозможно.

Type type = GetSomeType();
Object @object = GetSomeObject();

??? xyz = @object.CastTo(type); // How would you declare the variable?

xyz.??? // What methods, properties, or fields are valid here?
Даниэль Брюкнер
источник
3
Если вы используете универсальный класс, который определяет метод с возвращаемым значением типа T, вам может потребоваться это сделать. Например, парсинг строки в экземпляр T и возвращение этого.
Оливер Фридрих
7
К счастью, это не правильный ответ. Смотрите ответ maulik13.
rushinge
3
Где, во имя Небес, вы найдете CastToметод Object?
ProfK
3

Когда дело доходит до приведения к типу Enum:

private static Enum GetEnum(Type type, int value)
    {
        if (type.IsEnum)
            if (Enum.IsDefined(type, value))
            {
                return (Enum)Enum.ToObject(type, value);
            }

        return null;
    }

И ты назовешь это так:

var enumValue = GetEnum(typeof(YourEnum), foo);

Это было важно для меня в случае получения значения атрибута Description нескольких типов перечисления по значению int:

public enum YourEnum
{
    [Description("Desc1")]
    Val1,
    [Description("Desc2")]
    Val2,
    Val3,
}

public static string GetDescriptionFromEnum(Enum value, bool inherit)
    {
        Type type = value.GetType();

        System.Reflection.MemberInfo[] memInfo = type.GetMember(value.ToString());

        if (memInfo.Length > 0)
        {
            object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), inherit);
            if (attrs.Length > 0)
                return ((DescriptionAttribute)attrs[0]).Description;
        }

        return value.ToString();
    }

а потом:

string description = GetDescriptionFromEnum(GetEnum(typeof(YourEnum), foo));
string description2 = GetDescriptionFromEnum(GetEnum(typeof(YourEnum2), foo2));
string description3 = GetDescriptionFromEnum(GetEnum(typeof(YourEnum3), foo3));

В качестве альтернативы (лучший подход) такое приведение может выглядеть так:

 private static T GetEnum<T>(int v) where T : struct, IConvertible
    {
        if (typeof(T).IsEnum)
            if (Enum.IsDefined(typeof(T), v))
            {
                return (T)Enum.ToObject(typeof(T), v);
            }

        throw new ArgumentException(string.Format("{0} is not a valid value of {1}", v, typeof(T).Name));
    }
krzyski
источник
1

После того, как я не нашел ничего, что можно обойти, исключение «Объект должен реализовывать IConvertible» при использовании ответа Zyphrax (кроме реализации интерфейса). Я попробовал что-то немного необычное и сработало для моей ситуации.

Используя пакет Nugets Newtonsoft.Json ...

var castedObject = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(myObject), myType);
односложный
источник
1

Вред, проблема в том, что у тебя нет T.

у вас есть только переменная типа.

Подсказка для MS, если бы вы могли сделать что-то вроде

TryCast<typeof(MyClass)>

если бы решить все наши проблемы.

user2825546
источник
0

Я никогда не пойму, почему вам нужно до 50 репутации, чтобы оставить комментарий, но я просто должен был сказать, что ответ @Curt - это именно то, что я искал, и, надеюсь, кто-то еще.

В моем примере у меня есть атрибут ActionFilterAttribute, который я использовал для обновления значений документа json patch. Я не знал, что такое модель T для патч-документа, поэтому мне пришлось сериализовать и десериализовать его в простой JsonPatchDocument, изменить его, а затем, потому что у меня был тип, снова сериализовать и десериализовать его обратно в тип.

Type originalType = //someType that gets passed in to my constructor.

var objectAsString = JsonConvert.SerializeObject(myObjectWithAGenericType);
var plainPatchDocument = JsonConvert.DeserializeObject<JsonPatchDocument>(objectAsString);

var plainPatchDocumentAsString= JsonConvert.SerializeObject(plainPatchDocument);
var modifiedObjectWithGenericType = JsonConvert.DeserializeObject(plainPatchDocumentAsString, originalType );
Лей Элти Тайво
источник
-1
public bool TryCast<T>(ref T t, object o)
{
    if (
        o == null
        || !typeof(T).IsAssignableFrom(o.GetType())
        )
        return false;
    t = (T)o;
    return true;
}
user2008563
источник
2
Не могли бы вы указать, чем этот ответ отличается от других ответов и где это решение подходит?
Клаус Гюттер
-2

даже чище:

    public static bool TryCast<T>(ref T t, object o)
    {
        if (!(o is T))
        {
            return false;
        }

        t = (T)o;
        return true;
    }
Харм Саломонс
источник
-2

Если вам нужно приводить объекты во время выполнения, не зная типа назначения, вы можете использовать отражение для создания динамического преобразователя.

Это упрощенная версия (без кеширования сгенерированного метода):

    public static class Tool
    {
            public static object CastTo<T>(object value) where T : class
            {
                return value as T;
            }

            private static readonly MethodInfo CastToInfo = typeof (Tool).GetMethod("CastTo");

            public static object DynamicCast(object source, Type targetType)
            {
                return CastToInfo.MakeGenericMethod(new[] { targetType }).Invoke(null, new[] { source });
            }
    }

тогда вы можете назвать это:

    var r = Tool.DynamicCast(myinstance, typeof (MyClass));
marianop
источник