ArrayTypeMismatchException Source array type cannot be assigned to destination array type

Art Hansen 566 Reputation points
2021-01-27T14:06:39.793+00:00

I'm tying to get the values in a manually created array into a System based array.

Exception Details:
System.ArrayTypeMismatchException
Message=Source array type cannot be assigned to destination array type.
StackTrace:
at System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length, Boolean reliable)
at System.Array.Copy(Array sourceArray, Array destinationArray, Int32 length)
at TourneyManagerUI.PrizeTemplateWRKG.arrayCB(ControlCollection listControls) in D:\TournyManagerUI\PrizeTemplateWRKG.cs:line 75

Relevant Code

private CheckBox[] _Checks;
public PrizeTemplateWRKG() 
{
 InitializeComponent();
 arrayCB(Controls);
}

private void arrayCB(Control.ControlCollection listControls)
{
 List<string> cbList = new List<string>();
 for (int inc = 0; inc <= 10; inc++)
 {
 foreach (Control cbName in listControls)
 {
 int nameLen = cbName.Name.Length;
 string ctrlNamePrefix = cbName.Name.Substring(0, nameLen - 1);
 if (cbName.Name.StartsWith("LevelCheckBox0") == true)
 {
 string findCB = $"{ctrlNamePrefix}{inc}";
 if (cbName.Name == findCB) {   cbList.Add(findCB);   }
 }
 }
 }
 string[] isCB = cbList.ToArray();
 int indexCBs = ((isCB.GetUpperBound(0)) +1);
 _Checks = new CheckBox[indexCBs];
 Array.Copy(isCB, _Checks, indexCBs); //  <=== Exception thrown here
}

Objects "cbList", "isCB" and "indexCBs" all have an index value of 10 when hovering the cursor over them in Lines 24-26 whilst the VS exception window still displayed. On Line 27 where the exception is thrown, however, shows the following values:
isCB = {string[10]}
_Checks = {System.Windows.Forms.CheckBox[10]}
indexCBs = 10

Additionally the destination array (_Checks) shows a value of Boolean reliable which the source array (isCB) doesn't have. According to Microsoft documentation the source & destination arrays must be identical in order to perform ans Array.Copy. I don't know how to get the two arrays truly identical. Or maybe there's a better way of "copying" array values?

Any assistance will be appreciated,
Art

Windows Forms
Windows Forms
A set of .NET Framework managed libraries for developing graphical user interfaces.
1,907 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
11,101 questions
0 comments No comments
{count} votes

Accepted answer
  1. Michael Taylor 55,461 Reputation points
    2021-01-28T17:44:48.257+00:00

    The problem is you keep using Array. Stop using that. It is designed for when you truly have generic things and in this case you don't. Array is almost never needed.

    If I understand your request correctly you have a list of checkboxes named LevelCheckBox##. When a checkbox is checked all previously numbered ones are auto-checked. When it is unchecked all lower level check boxes are also unchecked. This isn't too hard to implement and you don't need Array to do it.

    private void OnChecked ( object sender, EventArgs e )
    {
        var target = sender as CheckBox;
    
        //Get the target's ID
        var targetId = GetId(target);
    
        //To save a little time, get all checkboxes, and their "number"
        // filtering out those checkboxes that don't have one
        var checkBoxes = from c in Controls.OfType<CheckBox>()
                            let id = GetId(c)
                            where id >= 0
                            select new { Id = id, Control = c };
    
        //If the check box is now checked            
        if (target.Checked)
        {
            //Get all the "earlier" check boxes and uncheck them
            var items = checkBoxes.Where(x => x.Id < targetId);
            foreach (var item in items)
                item.Control.Checked = true;
        }  else
        {
            //Get all the later check boxes and uncheck them
            var items = checkBoxes.Where(x => x.Id > targetId);
            foreach (var item in items)
                item.Control.Checked = false;
        };
    }
    
    private static int GetId ( Control control )
    {
        //This is slow so using Tag or something would be preferable
        var match = s_regex.Match(control.Name);
        if (match.Success && Int32.TryParse(match.Groups["id"]?.Value, out var id))
            return id;
    
        return -1;
    }
    
    private static Regex s_regex = new Regex(@"^LevelCheckBox(?<id>\d+)$", RegexOptions.Singleline|RegexOptions.IgnoreCase);
    

    Note that I wrote this code based upon your existing logic of using field names. Personally I would recommend you use something more maintainable such as a Tag value or perhaps just use the TabIndex. But the code I posted works irrelevant of how you determine the "less" and "greater" controls.


2 additional answers

Sort by: Most helpful
  1. Michael Taylor 55,461 Reputation points
    2021-01-27T15:25:17.817+00:00

    Array.Copy copies the contents of one array to another and they must be the same type. Trying to convert a string array to a CheckBox array won't work. The runtime has no idea how to do such a conversion.

    I see some issues with your code, most of which isn't doing things you don't need to do. So let's just simplify the code. The first thing I wonder about is where your magic number of 10 is coming from? In general you use a field to back the controls on a form so you don't need to programmatically search for them. But if you do programmatically add controls then this code is fine but the arbitrary 10 seems odd to me.

    The second thing I'm going to assume here is that you want the actual CheckBox control associated with the name, you don't want to create a new one as you're doing now.

    private CheckBox[] FindCheckBoxes ( Control.ControlCollection controls, string prefix )
    {
        //Filter all controls to just the checkboxes
        //Then filter by controls that start with the given prefix, case insensitive
        var matches = from c in controls.OfType<CheckBox>()
                        where c.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
                        select c;
    
        //Personally I would just return IEnumerable<CheckBox> but if you want an array...
        return matches.ToArray();
    }
    

  2. Art Hansen 566 Reputation points
    2021-01-28T17:09:08.533+00:00

    [using Answer as continuation of the Comment started above]
    @Michael Taylor Context
    I’m working on a winForm which presents 10 rows of controls with each row being controlled by the state of the leading checkbox.

    61561-tm-prizes45.jpg

    Selecting (checking) a lower level box “activates” all rows above and deselecting a higher level box “deactivates” all rows (if any) below it. This behavior is currently controlled via a “hard coded” array:

            private CheckBox[] _Checks;  
            public PrizeTemplateWRKG()   
            {  
                InitializeComponent();  
      
                _Checks = new CheckBox[]  
                { LevelCheckBox00, LevelCheckBox01, LevelCheckBox02, LevelCheckBox03,  
                  LevelCheckBox04, LevelCheckBox05, LevelCheckBox06, LevelCheckBox07,  
                  LevelCheckBox08, LevelCheckBox09 };  
            }  
            private void HandleCheckboxes( int mstrTag, string chkDirection)  
            {  
                if (chkDirection == "Chking")  
                {  
                        for (int x = 0; x <= mstrTag; x++)  
                        {   _Checks[x].Checked = true;    }  
                }  
                else if (chkDirection == "unChking")  
                {  
                        for (int x = mstrTag; x < _Checks.Length; x++)  
                        {   _Checks[x].Checked = false;~    }  
                }  
            }  
    

    Objective/Actions to date
    I’m attempting to replace instantiating the array using hard coded values (which is working) with programmatically populating the array values. The elements of the array returned by the “FindCheckBoxes” method are all “System.Windows.Forms.CheckBox, CheckState: 0” which is not triggering the checkbox check-state behavior on the form. I attempted a filtering – – essentially based on your code (I still think it's a great idea if I could get it to work) – – to reduce the code required to create the needed list/array

    Array cbs = (Array)Controls.OfType<CheckBox>().OrderBy(x => x.Name);  
    foreach (var item in cbs)  
    {   _Checks.Append < CheckBox >(item);    }  
      
    List<string> cbList2 = (List<string>)Controls.OfType<CheckBox>().OrderBy(x => x.Name);  
    string chksContent = string.Join(", ", cbList2);  
    Console.Write($"\n{chksContent}\n");  
    _Checks = new CheckBox[]  
    {   chksContent  };  
    

    but while the filter lines (1 & 5) compile they throw an exception at run-time.

    Unable to cast object of type 'System.Linq.OrderedEnumerable`2[System.Windows.Forms.CheckBox,System.String]' to type 'System.Array'.

    And, of course, the lines after the filtering statements won’t even compile.

    I keep hitting a “cannot convert from <(string or object) source> to "System.Windows.Forms..CheckBox” wall. I’ve also attempted the Converter<TInput,TOutput> (system.converter-2) approach trying with both lists and arrays but the result was still an array populated with “System.Windows.Forms.CheckBox, CheckState: 0” elements.

    Looking forward to your additional ideas...

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.