I can see following two ways of instantiating an int array in C#:
Through an API in System.Array
abstract class:
var arrayInstance = Array.CreateInstance(typeof(int), 4);
Through various array initialization syntax:
var arrayInstanceWithSyntax = new int[4];
Are the above two ways absolutely identical? Does the compiler converts the second syntax into first syntax at compile time itself (present in MSIL) or there is some JIT magic at CLR level which happens at run time or there is no conversion at all between the two code syntax?
They definitely create the same kind of value - unlike if you call Array.CreateInstance
and create an array with a non-zero lower bound, for example.
However, they're not the same in terms of IL - the first is simply a method call, the second uses the newarr
IL instruction.
There doesn't need to be any kind of "JIT magic" here - there are just two paths to create the same kind of value.
The compile-time type of your first variable is just Array
though - you'd have to cast it to int[]
for the two pieces of code to really have the same result.
I would always use the "C# native" array creation syntax where possible - only use Array.CreateInstance
when you have an element Type
for some reason (rather than knowing at compile time, even via a generic type parameter)... or if you're trying to create an array that may have a non-zero lower bound.
Short Answer: No they produce different IL. You can see this yourself on Try Roslyn.
Array.CreateInstance
The CreateInstance
method is a factory method in the Array
class and it returns a type of Array
. Here is the source code for the method:
[System.Security.SecuritySafeCritical] // auto-generated
public unsafe static Array CreateInstance(Type elementType, int length)
{
if ((object)elementType == null)
throw new ArgumentNullException("elementType");
if (length < 0)
throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
Contract.Ensures(Contract.Result<Array>() != null);
Contract.Ensures(Contract.Result<Array>().Length == length);
Contract.Ensures(Contract.Result<Array>().Rank == 1);
Contract.EndContractBlock();
RuntimeType t = elementType.UnderlyingSystemType as RuntimeType;
if (t == null)
throw new ArgumentException(Environment.GetResourceString("Arg_MustBeType"), "elementType");
return InternalCreate((void*)t.TypeHandle.Value, 1, &length, null);
}
Please note the last line of code in the above method. The body of that method is just a semi-colon and it is a method implemented externally elsewhere. Here is the body:
[System.Security.SecurityCritical] // auto-generated
[ResourceExposure(ResourceScope.None)]
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private unsafe static extern Array InternalCreate(void* elementType, int rank, int* pLengths, int* pLowerBounds);
Where is that implemented? It is implemented in arraynative.cpp class. Here is the code:
FCIMPL4(Object*, ArrayNative::CreateInstance, void* elementTypeHandle, INT32 rank, INT32* pLengths, INT32* pLowerBounds) {
{
CONTRACTL {
FCALL_CHECK;
PRECONDITION(rank > 0);
PRECONDITION(CheckPointer(pLengths));
PRECONDITION(CheckPointer(pLowerBounds, NULL_OK));
}
CONTRACTL_END;
OBJECTREF pRet = NULL;
TypeHandle elementType = TypeHandle::FromPtr(elementTypeHandle);
_ASSERTE(!elementType.IsNull());
// pLengths and pLowerBounds are pinned buffers. No need to protect them.
HELPER_METHOD_FRAME_BEGIN_RET_0();
CheckElementType(elementType);
CorElementType CorType = elementType.GetSignatureCorElementType();
CorElementType kind = ELEMENT_TYPE_ARRAY;
// Is it ELEMENT_TYPE_SZARRAY array?
if (rank == 1 && (pLowerBounds == NULL || pLowerBounds[0] == 0)
# ifdef FEATURE_64BIT_ALIGNMENT
// On platforms where 64-bit types require 64-bit alignment and don't obtain it naturally force us
// through the slow path where this will be handled.
&& (CorType != ELEMENT_TYPE_I8)
&& (CorType != ELEMENT_TYPE_U8)
&& (CorType != ELEMENT_TYPE_R8)
#endif
)
{
// Shortcut for common cases
if (CorTypeInfo::IsPrimitiveType(CorType))
{
pRet = AllocatePrimitiveArray(CorType, pLengths[0]);
goto Done;
}
else
if (CorTypeInfo::IsObjRef(CorType))
{
pRet = AllocateObjectArray(pLengths[0], elementType);
goto Done;
}
kind = ELEMENT_TYPE_SZARRAY;
pLowerBounds = NULL;
}
{
// Find the Array class...
TypeHandle typeHnd = ClassLoader::LoadArrayTypeThrowing(elementType, kind, rank);
DWORD boundsSize = 0;
INT32* bounds;
if (pLowerBounds != NULL)
{
if (!ClrSafeInt < DWORD >::multiply(rank, 2, boundsSize))
COMPlusThrowOM();
DWORD dwAllocaSize = 0;
if (!ClrSafeInt < DWORD >::multiply(boundsSize, sizeof(INT32), dwAllocaSize))
COMPlusThrowOM();
bounds = (INT32*)_alloca(dwAllocaSize);
for (int i = 0; i < rank; i++)
{
bounds[2 * i] = pLowerBounds[i];
bounds[2 * i + 1] = pLengths[i];
}
}
else
{
boundsSize = rank;
DWORD dwAllocaSize = 0;
if (!ClrSafeInt < DWORD >::multiply(boundsSize, sizeof(INT32), dwAllocaSize))
COMPlusThrowOM();
bounds = (INT32*)_alloca(dwAllocaSize);
// We need to create a private copy of pLengths to avoid holes caused
// by caller mutating the array
for (int i = 0; i < rank; i++)
bounds[i] = pLengths[i];
}
pRet = AllocateArrayEx(typeHnd, bounds, boundsSize);
}
Done:;
HELPER_METHOD_FRAME_END();
return OBJECTREFToObject(pRet);
}
As you can see Array.CreateInstance
uses an external DLL implemented elsewhere, outside of managed code.
new int[4];
This is native to C# so the C# compiler will take care of it and create the array. How? I am not sure.
I hope that clarifies things a little.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With