可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm looking for a way to write code that tests whether a value is boxed.
My preliminary investigations indicate that .NET goes out of its way to conceal the fact, meaning that GetType()
and IsValueType
don't reveal the difference between a boxed value and an unboxed value. For example, in the following LinqPad C# expressions, I have faith that o1
is boxed and i1
is not boxed, but I would like a way to test it in code, or, second best, a way to know FOR SURE when looking at any variable or value, even if its type is "dynamic" or "object," whether it's boxed or not boxed.
Any advice?
// 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");
回答1:
Try the following
public static bool IsBoxed<T>(T value)
{
return
(typeof(T).IsInterface || typeof(T) == typeof(object)) &&
value != null &&
value.GetType().IsValueType;
}
By using a generic we allow the function to take into account both the type of the expression as viewed by the compiler and it's underlying value.
Console.WriteLine(IsBoxed(42)); // False
Console.WriteLine(IsBoxed((object)42)); // True
Console.WriteLine(IsBoxed((IComparable)42)); // True
EDIT
A couple of people have asked for clarification on why this needs to be generic. And questioned why this is even needed at all, can't the developer just look at code and tell if a value is boxed? In an attempt to answer both those questions consider the following method signature
void Example<T>(T param1, object param2, ISomething param3) where T : ISomething {
object local1 = param1;
ISomething local2 = param1;
...
}
In this scenario any of the provided parameters or locals could potentially represent boxed values and could just as easily not be. It's impossible to tell by casual inspection, only an examination of a combination of the runtime type and the reference by which the value is held can determine that.
回答2:
Well, let's use the trick ...
What do we know?
- Value-type variable gets boxed again and again when assigned into reference-type variable
- Reference-type variable will not get boxed again ...
So we will just check whether it gets boxed again (into another object) ... so we compare references
isReferenceType
will be false here, because we compare 2 objects on heap (one boxed in surelyBoxed
, one boxed just in call to ReferenceEquals):
int checkedVariable = 123; //any type of variable can be used here
object surelyBoxed = checkedVariable;
bool isReferenceType = object.ReferenceEquals(surelyBoxed, checkedVariable);
isReferenceType
will be true here, because we compare 1 object on heap to itself:
object checkedVariable = 123; //any type of variable can be used here
object surelyBoxed = checkedVariable;
bool isReferenceType = object.ReferenceEquals(surelyBoxed, checkedVariable);
This works for ANY type, not just for int
and object
To put it into well-usable method:
public static bool IsReferenceType<T>(T input)
{
object surelyBoxed = input;
return object.ReferenceEquals(surelyBoxed, input);
}
This method can be easily used like this:
int i1 = 123;
object o1 = 123;
//...
bool i1Referential = IsReferenceType(i1); //returns false
bool o1Referential = IsReferenceType(o1); //returns true
回答3:
Here are some simple helper methods to check if a variable is a boxed integer:
public static bool IsBoxed(object item)
{
return true;
}
public static bool IsBoxed<T>(T item) where T : struct
{
return false;
}
Just call IsBoxed(...)
on your variable:
IsBoxed(o1) // evaluates to true
IsBoxed(i1) // evaluates to false
This accomplishes nothing, of course. Why exactly do you need to know if a value is boxed or not?
回答4:
GetType() and IsValueType don't reveal
the difference between a boxed value
and an unboxed value.
GetType
is a sealed (non-virtual) method on System.Object
. Calling this method on a value-type will definitely box it. Not even Nullable<T>
is able to get around this - calling GetType
on a nullable will return the underlying type if it has a value (boxed as the underlying type) or throw a NullReferenceException
if it doesn't (boxed to null, can't dereference a null-reference).
when looking at
any variable or value, even if its
type is "dynamic" or "object," whether
it's boxed or not boxed.
In general, if you have an expression that "holds" a value-type, the value of that expression will be a reference to a box unless the expression's compile-time type is of the value-type itself (generics are slightly more complicated). Common reference-types that can hold references to boxed structures are object
, dynamic
and interface-types.
回答5:
Similar to Allon's answer, but should return the correct answer for any type without generating a compile-time error:
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;
}
(Although you could make the argument that the possible compile-time error caused by Allon's code is a feature, not a bug: if you hit a compile-time error then you're definitely not dealing with an unboxed value type!)
回答6:
If a type is a value type and its static type is 'dynamic' or 'object', or an interface, it is always boxed.
If a type is a value type and its static type is the actual type, it is never boxed.
回答7:
I think actually the question is kind of misstated. Isn't the question actually, "How can I tell if an object is a box for another type?"
With reference to Allon's comment, if you have an object of type Object and the object is a primitive value type, it's a box. I'm not certain this is 100% correct, but (similar to Allon's implementation):
// Assume there is some object o.
bool isBoxed = o.GetType().IsPrimitive;
回答8:
This approach is similar to Jared Par's answer. But I think !typeof(T).IsValueType
is cleaner than enumerating all types which could contain a boxed value.
public static bool IsBoxed<T>(T value)
{
return !typeof(T).IsValueType && (value != null) && value.GetType().IsValueType;
}
Unlike Jared's code this will handle the case where T
is System.ValueType
correctly.
Another subtle point is that value.GetType().IsValueType
comes after !typeof(T).IsValueType
since otherwise GetType()
would create a temporary boxed copy of the value.
回答9:
I'm not sure if this will be relevant to anyone, but since I encountered this post because boxing was actually impacting my very dynamic mapping.
Sigil proivdes a fantastic UnBoxAny method
Assuming you have the following:
public class Report { public decimal Total { get; set; } }
new Dictionary<string, object> { { "Total", 5m} }
So the decimal value is boxed.
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:
* invoke TryGetValue
** use in If/Else