Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between Array.CreateInstance and using the new operator to create new array instance

I can see following two ways of instantiating an int array in C#:

  1. Through an API in System.Array abstract class:

    var arrayInstance = Array.CreateInstance(typeof(int), 4);
    
  2. 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?

like image 219
RBT Avatar asked Apr 24 '17 06:04

RBT


2 Answers

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.

like image 114
Jon Skeet Avatar answered Sep 30 '22 11:09

Jon Skeet


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.

like image 26
CodingYoshi Avatar answered Sep 30 '22 09:09

CodingYoshi