Как проверить, упаковано ли значение в C # / .NET? - PullRequest
25 голосов
/ 27 апреля 2011

Я ищу способ написать код, который проверяет, упаковано ли значение.

Мои предварительные исследования показывают, что .NET делает все возможное, чтобы скрыть этот факт, а это означает, что GetType() и IsValueType не показывают разницы между коробочным значением и незаписанным значением. Например, в следующих выражениях LinqPad C # я верю, что o1 в штучной упаковке, а i1 не в штучной упаковке, но я хотел бы проверить способ в коде или, во-вторых, узнать FOR SURE при просмотре любой переменной или значения, даже если его тип "динамический" или "объект", независимо от того, упакован он или нет.

Любой совет?

// boxed? -- no way to tell from these answers!
object o1 = 123;
o1.GetType().Dump("o1.GetType()");
o1.GetType().IsValueType.Dump("o1.GetType().IsValueType");

// not boxed? -- no way to tell from these answers!
int i1 = 123;
i1.GetType().Dump("i1.GetType()");
i1.GetType().IsValueType.Dump("i1.GetType().IsValueType");

Ответы [ 9 ]

33 голосов
/ 27 апреля 2011

Попробуйте следующее

public static bool IsBoxed<T>(T value)
{
    return 
        (typeof(T).IsInterface || typeof(T) == typeof(object)) &&
        value != null &&
        value.GetType().IsValueType;
}

Используя обобщенный тип, мы позволяем функции учитывать как тип выражения, видимого компилятором, так и его базовое значение.

Console.WriteLine(IsBoxed(42));  // False
Console.WriteLine(IsBoxed((object)42)); // True
Console.WriteLine(IsBoxed((IComparable)42));  // True

EDIT

Несколько человек попросили уточнить, почему это должно быть общим. И спросил, зачем это вообще нужно, разве разработчик не может просто посмотреть на код и сказать, упаковано ли значение? В попытке ответить на оба этих вопроса рассмотрим следующую подпись метода

void Example<T>(T param1, object param2, ISomething param3) where T : ISomething {
  object local1 = param1;
  ISomething local2 = param1;
  ...
}

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

6 голосов
/ 27 апреля 2011

Хорошо, давайте использовать трюк ...

Что мы знаем?

  • Переменная типа значения упаковывается снова и снова при назначении переменной типа ссылки
  • Переменная ссылочного типа не будет упакована снова ...

Итак, мы просто проверим, упакован ли он снова (в другой объект) ... поэтому мы сравним ссылки

isReferenceType будет false здесь, потому что мы сравниваем 2 объекта в куче (один упакован в surelyBoxed, один упакован только в вызове ReferenceEquals):

int checkedVariable = 123;     //any type of variable can be used here
object surelyBoxed = checkedVariable;
bool isReferenceType = object.ReferenceEquals(surelyBoxed, checkedVariable);

isReferenceType будет true здесь, потому что мы сравниваем 1 объект в куче с собой:

object checkedVariable = 123;     //any type of variable can be used here
object surelyBoxed = checkedVariable;
bool isReferenceType = object.ReferenceEquals(surelyBoxed, checkedVariable);

Это работает для ЛЮБОГО типа, а не только для int и object

Чтобы поместить его в удобный метод:

    public static bool IsReferenceType<T>(T input)
    {
        object surelyBoxed = input;
        return object.ReferenceEquals(surelyBoxed, input);
    }

Этот метод можно легко использовать следующим образом:

int i1 = 123;
object o1 = 123;
//...
bool i1Referential = IsReferenceType(i1);  //returns false
bool o1Referential = IsReferenceType(o1);  //returns true
4 голосов
/ 27 апреля 2011

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

public static bool IsBoxed(object item)
{
    return true;
}

public static bool IsBoxed<T>(T item) where T : struct
{
    return false;
}

Просто вызовите IsBoxed(...) для вашей переменной:

IsBoxed(o1) // evaluates to true
IsBoxed(i1) // evaluates to false

Это ничего не дает,конечно.Почему именно вам нужно знать, упаковано или нет значение?

4 голосов
/ 27 апреля 2011

GetType () и IsValueType не показывают разницу между упакованным значением и распакованным значением.

GetType является запечатанным (невиртуальный) метод на System.Object.Вызов этого метода для типа-значения определенно определит его.Даже Nullable<T> не может обойти это - вызов GetType для nullable вернет базовый тип, если он имеет значение (помечено как базовый тип), или бросит NullReferenceException, если он не имеет (упакован вnull, не может разыменовать нулевую ссылку).

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

В общем случае, если у вас есть выражение, которое «содержит» тип значения, значение этого выражения будет ссылкой на блок , если не является типом времени компиляции выраженияимеет тип значения (дженерики немного сложнее).Распространенными ссылочными типами, которые могут содержать ссылки на штучные структуры, являются object, dynamic и интерфейсные типы.

2 голосов
/ 28 апреля 2011

Аналогично ответу Аллона , но должен возвращать правильный ответ для любого типа без генерации ошибки времени компиляции:

int i = 123;
Console.WriteLine(IsBoxed(i));    // false

object o = 123;
Console.WriteLine(IsBoxed(o));    // true

IComparable c = 123;
Console.WriteLine(IsBoxed(c));    // true

ValueType v = 123;
Console.WriteLine(IsBoxed(v));    // true

int? n1 = 123;
Console.WriteLine(IsBoxed(n1));    // false
int? n2 = null;
Console.WriteLine(IsBoxed(n2));    // false

string s1 = "foo";
Console.WriteLine(IsBoxed(s1));    // false
string s2 = null;
Console.WriteLine(IsBoxed(s2));    // false

// ...

public static bool IsBoxed<T>(T item)
{
    return (item != null) && (default(T) == null) && item.GetType().IsValueType;
}

public static bool IsBoxed<T>(T? item) where T : struct
{
    return false;
}

(Хотя вы можете утверждать, что возможная ошибка во время компиляции, вызванная кодом Аллона, является функцией, а не ошибкой: если вы столкнулись с ошибкой во время компиляции, вы определенно не имеете дело с распакованным типом значения! )

2 голосов
/ 27 апреля 2011

Если тип является типом значения и его статическим типом является «динамический» или «объект», или интерфейс, он всегда в штучной упаковке.

Если тип является типом значения, а его статический тип является фактическим типом, он никогда в штучной упаковке.

1 голос
/ 28 апреля 2016

Я не уверен, будет ли это уместно кому-либо, но поскольку я столкнулся с этим постом, потому что бокс на самом деле влиял на мое очень динамичное отображение.

Символ обеспечивает фантастический метод UnBoxAny

Если у вас есть следующее:

public class Report { public decimal Total { get; set; } }

new Dictionary<string, object> { { "Total", 5m} }

Таким образом, десятичное значение в штучной упаковке.

var totalProperty = typeof(Report).GetProperty("Total");

var value = emit.DeclareLocal<object>();

//invoke TryGetValue on dictionary to populate local 'value'*
                                                    //stack: [bool returned-TryGetValue]
//either Pop() or use in If/Else to consume value **
                                                    //stack: 
//load the Report instance to the top of the stack 
//(or create a new Report)
                                                    //stack: [report]
emit.LoadLocal(value);                              //stack: [report] [object value]
emit.UnboxAny(totalProperty.PropertyType);          //stack: [report] [decimal value]

//setter has signature "void (this Report, decimal)" 
//so it consumes two values off the stack and pushes nothing

emit.CallVirtual(totalProperty.SetMethod);          //stack: 

* вызвать TryGetValue

** использовать в If / Else

1 голос
/ 21 августа 2015

Этот подход похож на ответ Джареда Пар.Но я думаю, что !typeof(T).IsValueType чище, чем перечисление всех типов, которые могут содержать значение в штучной упаковке.

public static bool IsBoxed<T>(T value)
{
    return !typeof(T).IsValueType && (value != null) && value.GetType().IsValueType;
}

В отличие от кода Джареда, он будет обрабатывать случай, когда T равен System.ValueType.

Еще один тонкий момент заключается в том, что value.GetType().IsValueType следует после !typeof(T).IsValueType, поскольку в противном случае GetType() создаст временную копию значения в штучной упаковке.

1 голос
/ 27 апреля 2011

Я думаю, что на самом деле вопрос несколько искажен.Разве не вопрос на самом деле: «Как я могу определить, является ли объект блоком для другого типа?»

Со ссылкой на комментарий Аллона, если у вас есть объект типа Object и объект является примитивным значениемтипа, это коробка.Я не уверен, что это на 100% правильно, но (похоже на реализацию Аллона):

// Assume there is some object o.
bool isBoxed = o.GetType().IsPrimitive;
...