views:

557

answers:

4

I write a large static method that takes a generic as a parameter argument. I call this method, and the framework throws a System.InvalidProgramException. This exception is thrown even before the first line of the method is executed.

I can create a static class which takes the generic argument, and then make this a method of the static class, and everything works fine.

Is this a .NET defect, or is there some obscure generic rule I'm breaking here?

For the sake of completeness, I've included the method which fails, and the method which passes. Note that this uses a number of other classes from my own library (eg GridUtils), and these classes are not explained here. I don't think the actual meaning matters: the question is why the runtime crashes before the method even starts.

(I'm programming with Visual Studio 2005, so maybe this has gone away in Visual Studio 2008.)

This throws an exception before the first line is invoked:

    private delegate void PROG_Delegate<TGridLine>(DataGridView dgv, IEnumerable<TGridLine> gridLines, string[] columns);

    public static void PopulateReadOnlyGrid<TGridLine>(DataGridView dgv, IEnumerable<TGridLine> gridLines, string[] columns)
    {
        if (dgv.InvokeRequired)
        {
            dgv.BeginInvoke
                        (
                            new PROG_Delegate<TGridLine>(PopulateReadOnlyGrid<TGridLine>),
                            new object[] { dgv, gridLines, columns }
                        );
            return;
        }
        GridUtils.StatePreserver statePreserver = new GridUtils.StatePreserver(dgv);
        System.Data.DataTable dt = CollectionHelper.ConvertToDataTable<TGridLine>((gridLines));
        dgv.DataSource = dt;
        dgv.DataMember = "";
        dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
        GridUtils.OrderColumns<TGridLine>(dgv, columns);
        statePreserver.RestoreState();
        dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
    }

This works fine:

    public static class Populator<TGridLine>
    {
        private delegate void PROG_Delegate(DataGridView dgv, IEnumerable<TGridLine> gridLines, string[] columns);

        public static void PopulateReadOnlyGrid(DataGridView dgv, IEnumerable<TGridLine> gridLines, string[] columns)
        {
            if (dgv.InvokeRequired)
            {
                dgv.BeginInvoke
                            (
                                new PROG_Delegate(PopulateReadOnlyGrid),
                                new object[] { dgv, gridLines, columns }
                            );
                return;
            }
            GridUtils.StatePreserver statePreserver = new GridUtils.StatePreserver(dgv);
            System.Data.DataTable dt = CollectionHelper.ConvertToDataTable<TGridLine>((gridLines));
            dgv.DataSource = dt;
            dgv.DataMember = "";
            dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            GridUtils.OrderColumns<TGridLine>(dgv, columns);
            statePreserver.RestoreState();
            dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;

        }
    }
A: 

Just FYI, not sure if it'll fix anything, but your Invoke method can be simplified. This also removes the need for that delegate (possibly leading to a fix?):

dgv.BeginInvoke(new MethodInvoker(delegate()
{
    PopulateReadOnlyGrid(dgv, gridLines, columns);
}));

Your code runs fine for me when I paste it in a form (after commenting out your GridUtils stuff). I even call the method both from the gui thread and the non-gui thread. I tried it in 3.5 and 2.0. Works fine.... (!?)

Try this code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Threading;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private delegate void PROG_Delegate<TGridLine>(Control dgv, IEnumerable<TGridLine> gridLines, string[] columns);

        public static void PopulateReadOnlyGrid<TGridLine>(Control dgv, IEnumerable<TGridLine> gridLines, string[] columns)
        {
            if (dgv.InvokeRequired)
            {
                dgv.BeginInvoke
                            (
                                new PROG_Delegate<TGridLine>(PopulateReadOnlyGrid<TGridLine>),
                                new object[] { dgv, gridLines, columns }
                            );
                return;
            }
            MessageBox.Show("hi");
            //GridUtils.StatePreserver statePreserver = new GridUtils.StatePreserver(dgv);
            //System.Data.DataTable dt = CollectionHelper.ConvertToDataTable<TGridLine>((gridLines));
            //dgv.DataSource = dt;
            //dgv.DataMember = "";
            //dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            //GridUtils.OrderColumns<TGridLine>(dgv, columns);
            //statePreserver.RestoreState();
            //dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            PopulateReadOnlyGrid(this, new int[] { 1, 2, 3 }, new string[] { "a" });

            ThreadPool.QueueUserWorkItem(new WaitCallback((a) =>
            {
                PopulateReadOnlyGrid(this, new int[] { 1, 2, 3 }, new string[] { "a" });
            }));

        }

    }
}
TheSoftwareJedi
A: 

Is this for ASP.NET, WinForms, or what? What is the GridUtils namespace?

GregUzelac
The GridUtils namespace is part of my own library. I'll clarify that it the message, thanks.
Andrew Stapleton
In direct answer to your question, it happens to be in WinForms.
Andrew Stapleton
A: 

Updated due to me misinterpreting the code example.

Try wrapping the delegate with a MethodInvoker:

http://msdn.microsoft.com/en-us/library/system.windows.forms.methodinvoker.aspx

jezell
TGridLine isn't specified by the parent class. In fact, he left out the parent class definition all together in his first example. It's treating them properly.
TheSoftwareJedi
Darn, there's a class I wish I'd known about. My code is littered with specialised delegates I'll be happy to get rid of.
Andrew Stapleton
A: 

Following TheSoftwareJedi's suggestion, I ran some tests, and conclusively proved that it's the use of the PROG_Delegate that causes the exception.

If I use MethodInvoker, the code runs without error.

    private delegate void PROG_Delegate<TGridLine>(DataGridView dgv, IEnumerable<TGridLine> gridLines, string[] columns);

    public static void PopulateReadOnlyGrid<TGridLine>(DataGridView dgv, IEnumerable<TGridLine> gridLines, string[] columns)
    {
        if (dgv.InvokeRequired)
        {
            dgv.BeginInvoke(new MethodInvoker(delegate()
            {
                PopulateReadOnlyGrid(dgv, gridLines, columns);
            }));
            return;
        }
        GridUtils.StatePreserver statePreserver = new GridUtils.StatePreserver(dgv);
        System.Data.DataTable dt = CollectionHelper.ConvertToDataTable<TGridLine>((gridLines));
        dgv.DataSource = dt;
        dgv.DataMember = "";
        dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
        GridUtils.OrderColumns<TGridLine>(dgv, columns);
        statePreserver.RestoreState();
        dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
    }

If I put the PROG_Delegate back in and remove everything else I get the exception back.

    private delegate void PROG_Delegate<TGridLine>(DataGridView dgv, IEnumerable<TGridLine> gridLines, string[] columns);

    public static void PopulateReadOnlyGrid<TGridLine>(DataGridView dgv, IEnumerable<TGridLine> gridLines, string[] columns)
    {
        if (dgv.InvokeRequired)
        {
            dgv.BeginInvoke
                        (
                            new PROG_Delegate<TGridLine>(PopulateReadOnlyGrid<TGridLine>),
                            new object[] { dgv, gridLines, columns }
                        );
        }
        //GridUtils.StatePreserver statePreserver = new GridUtils.StatePreserver(dgv);
        //System.Data.DataTable dt = CollectionHelper.ConvertToDataTable<TGridLine>((gridLines));
        //dgv.DataSource = dt;
        //dgv.DataMember = "";
        //dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
        //GridUtils.OrderColumns<TGridLine>(dgv, columns);
        //statePreserver.RestoreState();
        //dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
    }

So, there's an easy workaround, and the use of the MethodInvoker makes the code more succinct and readable.

On a theoretical level, there's still the question as to why it's crashing, given that the PROG_Delegate is legal and it's working for other people. I suspect the best answer we can come to is "some obscure bug", which will remain obscure because coders prefer to use Method Invoker anyway.

Andrew Stapleton