Search code examples
c#arraystypesduck-typing

Simplify Method for extending/shrinking range of arrays (duck typing


I'm trying to simplify a method that adds an additional entry to a range of arrays. Here is the current script:

private static void AddEntryToSettingsArrays()
{
    string[] Temparray1 = GlobalVariables.Array1; // Copys the setting array "1" to a temporary array.
    int[] Temparray2 = GlobalVariables.Array2; // Copys the setting array "2" to a temporary array.
    //...
    GlobalVariables.ArrayCount++; // Increments the Array count by one.
    GlobalVariables.Array1 = new string[GlobalVariables.ArrayCount]; // Clears array "1" and creates a new array with the new array count.
    GlobalVariables.Array2 = new int[GlobalVariables.ArrayCount]; // Clears array "2" and creates a new array with the new array count.
    //...
    for (int ArrayID = 0; ArrayID < GlobalVariables.ArrayCount - 1; ArrayID++) //Loops through the arrays until the next to last array is reached.
        {
        GlobalVariables.Array1[ArrayID] = Temparray1[ArrayID]; // Copys the "1" temporary array back to the global array.
        GlobalVariables.Array2[ArrayID] = Temparray2[ArrayID]; // Copys the "2" temporary array back to the global array.
        //...
        }
}

Depending on the range of arrays this this method is getting f... big and difficult to manage.

To simplify the function i have created an dictionary of the possible arrays:

private static BatchArraysDictionary CreateBatchArrayDictionary()
{
    BatchArraysDictionary PossibleBatchArrays = new BatchArraysDictionary(); // Creates a new "Batch Array Dictionary".
    PossibleBatchArrays.Add(nameof(GlobalVariables.Array1), GlobalVariables.Array1.GetType());
    PossibleBatchArrays.Add(nameof(GlobalVariables.Array2), GlobalVariables.Array2.GetType());
    //...
    return PossibleBatchArrays;
}

Now I'm trying to use that dictionary to simplified that function:

private static void AddEntryToSettingsArrays()
{
    BatchArraysDictionary CurrentArrays = CreateBatchArrayDictionary();
    GlobalVariables.ArrayCount++; // Increments the folder count by one.

    foreach (KeyValuePair<string, Type> CurrentArray in CurrentArrays)
    {
        Type CurrentType = CurrentArray.Value;
        var TempArray = typeof(GlobalVariables).GetField(CurrentArray.Key).GetValue(CurrentType);
        var GlobalArray = new CurrentType[GlobalVariables.ArrayCount]; // Getting the is a 'Type' but is used like a 'Variable' Error here...
        for(int ArrayID = 0; ArrayID < GlobalVariables.ArrayCount - 1; ArrayID++) //Loops through the Folders until the next to last folder is reached.
        {
            // Not sure what to do here jet (What's the equivalent to "GlobalVariables.Array1[ArrayID] = Temparray1[ArrayID];")
        }
    }

But I'm getting a "is a 'Type' but is used like a 'Variable'" Error on line 10. Additionally I have not a clue jet how to set the global variable like in:

GlobalVariables.Array1[ArrayID] = Temparray1[ArrayID];

Maybe someone of you can help me with my problem.

---------------- Update 2016-05-11:

I have tried to improve my method but know I'm getting Invalid Cast Exceptions or Access violations. (Depending on the type of variable)

private static void AddEntryToSettingsArrays()
{
    BatchArraysDictionary CurrentArrays = CreateBatchArrayDictionary();
    GlobalVariables.FolderCount++; // Increments the folder count by one.
    foreach (KeyValuePair<string, Type> CurrentArray in CurrentArrays)
    {
        Type ArrayType = CurrentArray.Value;
        //Console.WriteLine("Array Type (Stored): " + ArrayType);
        FieldInfo TempArrayField = typeof(GlobalVariables).GetField(CurrentArray.Key);
        //Console.WriteLine("Array Type (Field): " + TempArrayField.GetValue(typeof(GlobalVariables)));
        dynamic[] TempSourceArray = (dynamic[])TempArrayField.GetValue(typeof(GlobalVariables));
        //Console.WriteLine("Array loaded.");
        dynamic[] TempDestinationArray = new dynamic[GlobalVariables.FolderCount];
        //Console.WriteLine("New Array created.");
        for (int ArrayID = 0; ArrayID < GlobalVariables.FolderCount - 1; ArrayID++) //Loops through the Folders until the next to last folder is reached.
        {
            TempDestinationArray[ArrayID] = TempSourceArray[ArrayID];
            //Console.WriteLine("Array ID " + ArrayID + " was copied to new array");
        }
        //Console.WriteLine("Copyprccess finished.");
        //Console.WriteLine("Testentry:" + TempDestinationArray[0]);
        TempArrayField.SetValue(typeof(GlobalVariables), Convert.ChangeType(TempDestinationArray, ArrayType));
    }
}

I have tried to convert the current array to a dynamic variable but setting but setting the new value on the last line...

TempArrayField.SetValue(typeof(GlobalVariables), Convert.ChangeType(TempDestinationArray, ArrayType));

...fails with an invalid cast exception.

Additionally if the array is of type int[] or bool[] it already fails at line...

dynamic[] TempSourceArray = (dynamic[])TempArrayField.GetValue(typeof(GlobalVariables));

with an "Access violation".

Maybe I made clear now what's I'm trying to achieve.


Solution

  • For everyone who is curious, I found a solution for my problem.

    Here is what I came up with:

    First of all I changed my dictionary of arrays to a list, because just the names are needed now:

    /// <summary>
    /// Class that contain the names of arrays used to save settings.
    /// </summary>
    private class ArrayNamesList : List<string>
    {
        /// <summary>
        /// Adds an array name to the list.
        /// </summary>
        /// <param name="GlobalVariableName">Name of the array variable.</param>
        internal new void Add(string GlobalVariableName)
        {
            base.Add(GlobalVariableName);
        }
    }
    

    Used like in this method:

    /// <summary>
    /// Method that creates a list of possible batch setting array names.
    /// </summary>
    /// <returns>Returns a list of possible batch setting array names.</returns>
    private static ArrayNamesList CreateArrayDictionary()
    {
        BatchArrayNamesList PossibleBatchArrays = new BatchArrayNamesList(); // Creates a new "Batch Array Names List".
        PossibleBatchArrays.Add(nameof(GlobalVariables.ArrayOne)); // Adds the setting "1" name to the List.
        PossibleBatchArrays.Add(nameof(GlobalVariables.ArrayTwo)); // Adds the setting "2" name to the List.
        // ...
        return PossibleBatchArrays;
    }
    

    Then I have created two functions to extend an array and remove an entry from an array:

    Extend:

    /// <summary>
    /// Function that increases an array of unknown type to a certain length.
    /// </summary>
    /// <param name="CurrentArray">The array that needs to be increased in size.</param>
    /// <param name="NewArraySize">The new size of the array.</param>
    private static void ArrayResize(ref Array CurrentArray, int NewArraySize)
    {
        if (CurrentArray.Length < NewArraySize) // Checks if the new array size is larger than the old size.
        {
            Type elementType = CurrentArray.GetType().GetElementType(); // Gets the type of the arrays elements.
            Array newArray = Array.CreateInstance(elementType, NewArraySize); // Creats a new array with the new length.
            Array.Copy(CurrentArray, newArray, CurrentArray.Length); // Transfers the data from the old array to the new array.
            CurrentArray = newArray; // Replaces the old array with the new array.
        }
        else
        {
            Debugging.ErrorHandling.ErrorHandler("The new array size is not larger than the old array size.", 1201); // Creates an event log entry and error messagebox if application is run with gui.
        }
    }
    

    Used like in this method:

    /// <summary>
    /// Method that adds a new empty entry to all setting arrays.
    /// </summary>
    private static void AddEntryToSettingsArrays()
    {
        ArrayNamesList CurrentArrays = CreateArrayDictionary(); // Creates a list of the currently used setting arrays.
        GlobalVariables.FolderCount++; // Increments the folder count by one.
        foreach (string CurrentArray in CurrentArrays) // Loops through the list of arrays.
        {
            FieldInfo TempArrayField = typeof(GlobalVariables).GetField(CurrentArray); // Gets the current array with reflection.
            Array TempArray = (Array)TempArrayField.GetValue(typeof(GlobalVariables)); // Gets value of the current array.
            ArrayResize(ref TempArray, GlobalVariables.FolderCount); // Resizes the array.
            Type ArrayType = TempArray.GetType().GetElementType(); // Gets the type of the array.
            TempArrayField.SetValue(ArrayType, TempArray); // Replaces the old with the new array.
        }
    }
    

    Remove Entry:

    /// <summary>
    /// Function that removes an entry from an array of unknown type.
    /// </summary>
    /// <param name="CurrentArray">The array an entry needs to be deleted from.</param>
    /// <param name="NewArraySize">The new size of the array.</param>
    /// <param name="RemoveArrayID">The ID of the arrays entry that needs to be removed.</param>
    private static void ArrayResize(ref Array CurrentArray, int NewArraySize, int RemoveArrayID)
    {
        if
        (
            NewArraySize <= 0 // Checks if the new array size is not negative.
            && (CurrentArray.Length - 1) == NewArraySize // And if the new array size is the same than the old array size minus one entry.
        )
        {
            Type elementType = CurrentArray.GetType().GetElementType(); // Gets the type of the arrays elements.
            Array newArray = Array.CreateInstance(elementType, NewArraySize);  // Creats a new array with the new length.
            if (NewArraySize != 0) // if the new array is not empty.
            {
                int PositionStart = RemoveArrayID + 1; // Calculates the the start position of the entrys behind the removed ID.
                int RemainingLength = newArray.Length - RemoveArrayID; // Calculates the length of the array behind the removed ID.
                if (RemoveArrayID > 0) // If the array ID that should be remove is not the first entry.
                {
                    Array.Copy(CurrentArray, 0, newArray, 0, RemoveArrayID); // Copys the entrys in front of the removed ID in the new array.
                }
                else if (RemainingLength > 0) // If the array ID that should be remove is not the last entry.
                {
                    Array.Copy(CurrentArray, PositionStart, newArray, RemoveArrayID, RemainingLength); // Copys the entrys behind the removed ID in the new array.
                }
            }
            CurrentArray = newArray; // Replaces the old array with the new array.
        }
        else
        {
            Debugging.ErrorHandling.ErrorHandler("The new array size has an negative value or is not one entry smaller that the old array size.", 1202); // Creates an event log entry and error messagebox if application is run with gui.
        }
    }
    

    Used like in this method:

    /// <summary>
    /// Method that removes a certain entry from all setting arrays.
    /// </summary>
    /// <param name="RemoveID">ID of the setting that should be removed from the settings.</param>
    private static void RemoveEntryFromSettingsArrays(int RemoveID)
    {
        ArrayNamesList CurrentArrays = CreateArrayDictionary(); // Creates a list of the currently used setting arrays.
        GlobalVariables.FolderCount--; // Decrements the folder count by one.
        foreach (string CurrentArray in CurrentArrays) // Loops through the list of arrays.
        {
            FieldInfo TempArrayField = typeof(GlobalVariables).GetField(CurrentArray);// Gets the current array with reflection.
            Array TempArray = (Array)TempArrayField.GetValue(typeof(GlobalVariables)); // Gets value of the current array.
            ArrayResize(ref TempArray, GlobalVariables.FolderCount, RemoveID); // Removes an ID from the array.
            Type ArrayType = TempArray.GetType().GetElementType(); // Gets the type of the array.
            TempArrayField.SetValue(ArrayType, TempArray); // Replaces the old with the new array.
        }
    }