Подтвердить что ты не робот

Как проверить, включено ли значение в С#/.NET?

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

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

Любые советы?

// 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");
4b9b3361

Ответ 1

Попробуйте выполнить

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

ИЗМЕНИТЬ

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

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

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

Ответ 2

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

Что мы знаем?

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

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

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, потому что мы сравниваем один объект с кучей:

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

Ответ 3

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

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

при просмотре любой переменной или стоимости, даже если ее тип - "динамический" или "объект", он в коробке или без коробки.

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

Ответ 4

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

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

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

Ответ 5

Если тип - тип значения, а его статический тип - "динамический" или "объект" или интерфейс, он всегда помещается в поле.

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

Ответ 6

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

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

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

Ответ 7

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

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

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

Ответ 8

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

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

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

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

Ответ 9

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

Sigil выдает фантастический метод 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