Кастинг змінної за допомогою змінної Type


281

Чи можу я занести змінну об'єкта типу до змінної типу T, де T визначено в змінній Type?


12
Це не суто тематично, але ви здаєтеся досить нечіткими щодо того, що означає "кастинг", що, можливо, було б добре зрозуміти, яка мета та семантика оператора кастингу. Ось вдалий початок: blogs.msdn.com/ericlippert/archive/2009/03/19/…
Ерік Ліпперт

2
Я думав, що щось придумав. Якщо у вас є Typeзмінна, ви можете використовувати відображення для створення екземпляра цього типу. І тоді ви можете використовувати загальний метод для повернення потрібного типу, виводячи його з параметра цього типу. На жаль, будь-який метод відображення, який створює екземпляр типу, матиме тип повернення object, тому CastByExampleбуде використаний objectі ваш загальний метод . Тож насправді немає способу зробити це, і навіть якби він був, що б ви зробили з нововідданим об’єктом? Ви не можете використовувати його методи чи що-небудь, тому що ви не знаєте його типу.
Кайл Делані

@KyleDelaney Дякую, я повністю згоден! Як я намагався пояснити у своїй відповіді, насправді не так корисно накидати щось на іншу річ, не в якийсь момент визначаючи Тип, який ви насправді використовуєте. Вся суть типів - перевірка часу компілятора. Якщо вам просто потрібно робити дзвінки по об’єкту, ви можете використовувати objectабо dynamic. Якщо ви хочете динамічно завантажувати зовнішні модулі, ви можете змусити класи спільно використовувати спільний інтерфейс і передати об'єкт цьому. Якщо ви не керуєте стороннім кодом, створіть невеликі обгортки та реалізуйте на цьому інтерфейс.
Зифракс

Відповіді:


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))забезпечує рішення. TheConvert.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змінній.
  • Генерика може бути чудовим способом створення коду для багаторазового використання, який застосовується до багатьох різних типів, без необхідності знати точні типи.
  • Якщо ви застрягли, то розгляньте інший підхід або рефактор коду. Чи дійсно ваш код повинен бути таким динамічним? Чи має він враховувати будь-який тип?

145
Я не знаю, як це допомагає ОП. Вона має змінну типу, а не Tяк таку.
nawfal

12
@nawfal, в основному лінія Convert.ChangeType(input, typeof(T));дає рішення. Ви можете легко замінити typeof(T)наявну змінну типу. Кращим рішенням (якщо можливо) було б запобігти динамічному типу всі разом.
Zyphrax

59
@Zyphrax, ні, він все ще потребує акторів, до Tяких недоступний.
nawfal

4
Я знаю, що результуючий об'єкт насправді має тип, Tале все-таки ви отримуєте objectлише посилання. Хм, мені це питання було цікавим, якщо в ОП є лише Typeзмінна та інша інформація. Наче метод підпису Convert(object source, Type destination):) Тим не менш, я отримую ур точку
nawfal

10
Як це рішення цього питання? У мене така ж проблема, і у мене немає загальної <T>. У мене є лише змінна тип.
Нурі Тасдемір

114

В інших відповідях не згадується "динамічний" тип. Отже, щоб додати ще одну відповідь, ви можете використовувати тип "динамічний", щоб зберігати отриманий об'єкт без необхідності викидати перетворений об'єкт зі статичним типом.

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

Майте на увазі, що за допомогою "динамічного" компілятор обходить перевірку статичного типу, яка може ввести можливі помилки виконання, якщо ви не будете обережні.


19
Це правильна відповідь. Без динамічного ключового слова typeof (changeObj) є "об'єкт". З динамічним ключовим словом воно працює бездоганно, а 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ключовим словом, перетворюються лише основні дані об'єкта. Під час компіляції нам залишається кропітко розібратися, яким саме типом може бути наш об’єкт, що робить це рішення непрактичним. Я написав це як хак, щоб викликати операторів перетворення, визначених довільними типами, але, можливо, хтось там може знайти кращий варіант використання.


2
Потрібноusing System.Linq.Expressions;
Аарон Д

4
Для мене це страждає від тієї ж проблеми, що і у відповіді Зіфракса. Я не можу викликати методи на поверненому об'єкті, оскільки він все ще має тип "об'єкт". Я використовую його метод ("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 під час компіляції, а не під час виконання, вам потрібно передати його за допомогою змінної типу або просто власне імені типу. Можна зробити перше, використовуючи відповідь Зафракса.
Ешлі

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 # вимагає статичного набору тексту, щоб мати змогу зробити корисну справу з об'єктом
Мехрдад Афшарі

Ти маєш рацію. Я знаю очікувані типи двох змінних, які надсилаються методу як тип 'object'. Я хочу привести до очікуваних типів, що зберігаються у змінних, і додати їх до колекції. Набагато простіше розгалужувати тип і спробувати звичайні помилки відтворення та лову.
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?
ПрофК

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

1

Не знайшовши нічого, щоб обійти виняток "Об'єкт повинен реалізувати IConvertible" виняток при використанні відповіді Zyphrax (за винятком реалізації інтерфейсу). Я спробував щось трохи нетрадиційне і працював у своїй ситуації.

Використання нукетного пакету Newtonsoft.Json ...

var castedObject = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(myObject), myType);

1

Шкода, проблема в тому, що ти не маєш Т.

у вас є лише змінна типу.

Підкажіть MS, якщо ви могли зробити щось подібне

TryCast<typeof(MyClass)>

якби вирішити всі наші проблеми.


0

Я ніколи не зрозумію, чому вам потрібно до 50 репутації, щоб залишити коментар, але я просто повинен був сказати, що відповідь @Curt - це саме те, що я шукав і, сподіваюся, хтось інший.

У своєму прикладі у мене є ActionFilterAttribute, який я використовував для оновлення значень патч-документа json. Я не зробив те, що було 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;
}

2
Скажіть, будь ласка, чим ця відповідь відрізняється від інших відповідей і де це рішення підходить?
Клаус Гюттер


-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));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.