DataGridView exception: index -1 does not have a value?

elkinm

Platinum Member
Jun 9, 2001
2,146
0
71
I have a datagridview which has a table as the datasource.
I am trying to create a BindingSource in between so I can suspend the binding when updating the table.

This works:
Code:
DataTable table = new DataTable();
datagridview1.DataSource = table;
this does not work:
Code:
DataTable table = new DataTable();
BidningSource tablesource = new BindingSource();
tablesource.DataSource = table;
datagridview1.DataSource = tablesource;
I get the exception when launching the application.

I tried doing: tablesource.DataSource = table.DefaultView; and still got the exception.
If I do: tablesource.DataSource = table.DataSet; I don't get the exception, but at the same time the binding is not working and the datagridview is always empty when the table is populated.
What am I missing?

Thanks
 
Last edited:

elkinm

Platinum Member
Jun 9, 2001
2,146
0
71
You’re welcome. Unfortunately it still is not a final solution.

My goal is to use it in a multithreaded environment. The datagridview is in a form in a separate thread, the table is in the main thread as is the data source.

I am doing it this way so I can close or open the display window at any time and it is does not slow down the main application when running.

When needed, I set RaiseListChangeEvents = False, change the table as needed and then set RaiseListChangeEvents = true. But then when I set tablesource.ResetBindings(false) I get a cross threading exception.

This only happens if the window is open when the table update occurs.
This does not make sense to me since the table and the datasource are part of the main thread and it works just fine if I bind datagridview to the table directly.
I don’t understand exactly how the resetbindings option works. Is it possible to not call it after every RaiseListChangedEvents = true?
 

elkinm

Platinum Member
Jun 9, 2001
2,146
0
71
I think resetbinding is exactly what re-binds the data source. I noticed that if I commented out the resetbinding line the datagrid would not update, but if I would re-open the window it would have all the lines displayed.

This is exactly what I want to avoid because I expect the binding to remain active, I just want the table to not send updates until complete.
It seems in actuality it is just un-binding and binding at the BindingSource level.

I will try creating a second binding source before after the table. The goal of this is stop the binding from the table, but have the second thread be completely unaware that anything happened.

EDIT trying a second binding was not successful.
This is how it looks like now:

table -----> BidningSource1 ------> BindingSource2 -----> DataGridView.

I was hoping that if I stop BindingSource1 the binding between source2 and the DGV would remain, but it seems that the reset bindings goes through and resets the entire line.

Table and DataGridView are in different threads and I need some way to pause and resume the updates and maintain bindings?

Thanks again.
 
Last edited:

Oyster

Member
Nov 20, 2008
151
0
0
Can you post some code? See if you can post the code for the thread that populates the data and that of the form. It sounds like a synchronization issue, but it is hard to decipher without looking at the code.

If you want, you can upload your project to some place and post the link. I (we) can try and load it in VS locally.
 

elkinm

Platinum Member
Jun 9, 2001
2,146
0
71
My entire project is too big and is not something I can post here. I will try to post some code that should cover how it works.
 

elkinm

Platinum Member
Jun 9, 2001
2,146
0
71
Code:
namespace WindowsFormsApplication1
{
    public partial class Form2 : Form
    {
        Form1 myParent;
        DataTable myTable;
        BindingSource mySource;

        public delegate void ShowLogDelegate();
        public ShowLogDelegate showLogdelegate = null;

        public Form2(Form1 parent)
        {
            InitializeComponent();
            myParent = parent;
            showLogdelegate = new ShowLogDelegate(ShowLog);
            myTable = myParent.table;
            mySource = myParent.tableSource;
            //dataGridView1.DataSource = mySource;
            dataGridView1.DataSource = myTable;
            if (dataGridView1.ColumnCount > 0)
            {
                dataGridView1.Columns[0].DefaultCellStyle.Format = "HH:mm:ss.fff";
                foreach (DataGridViewColumn C in dataGridView1.Columns)
                {
                    C.SortMode = DataGridViewColumnSortMode.NotSortable;
                }
            }
            ShowLog();
        }

        public void ShowLog()
        {
            this.Activate();
        }
    }
}

This is the second form in it's entirety. I launch it as a second thread from Form1 with Form1 in the constructor. If table source is Form1.Table everything is fine, but if it is the DataSource and then there are issues.
 

elkinm

Platinum Member
Jun 9, 2001
2,146
0
71
This is the Logging Thread, which also runs on a different thread.

Code:
        public void LogLoop()
        {
            DateTime now = DateTime.Now;
            string time = now.ToString("HH.mm.ss");
            string date = now.ToString("yyyyddMM");
            string appPath = Path.GetDirectoryName(Application.ExecutablePath);
            string dirPath = appPath + "\\Logs";
            if (!Directory.Exists(dirPath))
            {
                Directory.CreateDirectory(dirPath);
            }
            string newSWName = "Tutorial Log " + date + "-" + time + ".log";
            newSWName = dirPath + "\\" + newSWName;
            
            sw = new StreamWriter(newSWName, true);
            sw.AutoFlush = true;
            //parentForm.table.Rows.Add(time, Action);
            sw.WriteLine("DateTime,Action");
            while (Logging)
            {
                if (que2.Count > 0)
                {
                    if (Monitor.TryEnter(que2))
                    {
                        try
                        {
                            //tableSource1.RaiseListChangedEvents = false;
                            while (que2.Count > 0)
                            {
                                Array e = que2.Dequeue();
                                string Date = ((DateTime)e.GetValue(0)).ToString("HH:mm:ss.fff");
                                string Action = (String)e.GetValue(1);
                                sw.WriteLine(Date + "," + Action);
                                table.Rows.Add((DateTime)e.GetValue(0), Action);
                            }
                        }
                        finally
                        {
                            Monitor.Exit(que2);
                            if (que1.Count > 0)
                            {
                                if (Monitor.TryEnter(que1))
                                {
                                    try
                                    {
                                        while (que1.Count > 0)
                                        {
                                            Array e = que1.Dequeue();
                                            string Date = ((DateTime)e.GetValue(0)).ToString("HH:mm:ss.fff");
                                            string Action = (String)e.GetValue(1);
                                            sw.WriteLine(Date + "," + Action);
                                            table.Rows.Add((DateTime)e.GetValue(0), Action);
                                        }
                                    }
                                    finally
                                    {
                                        Monitor.Exit(que1);
                                    }
                                }
                            }
                            //tableSource1.RaiseListChangedEvents = true;
                            //tableSource1.ResetBindings(false);
                        }
                    }
                }
                else


                {
                    if (que1.Count > 0)
                    {
                        if (Monitor.TryEnter(que1))
                        {
                            try
                            {
                                //tableSource1.RaiseListChangedEvents = false;
                                while (que1.Count > 0)
                                {
                                    Array e = que1.Dequeue();
                                    string Date = ((DateTime)e.GetValue(0)).ToString("HH:mm:ss.fff");
                                    string Action = (String)e.GetValue(1);
                                    sw.WriteLine(Date + "," + Action);
                                    table.Rows.Add((DateTime)e.GetValue(0), Action);
                                }
                            }
                            finally
                            {
                                Monitor.Exit(que1);
                                //tableSource1.RaiseListChangedEvents = true;
                                //tableSource1.ResetBindings(false);
                            }
                        }
                    }
                }

                Thread.Sleep(50);
            }
        }

It writes to a SW as well as the table. The main thread writes to que1 and if it is locked it goes to que2 as the backup. This never fails, and if I don't pause and restart the bindings as is commented out, everything works fine.
This is part of Form1 and the Table and que1 and que2 are part of Form1 as well.

Notice that I am locking on the que objects themselves instead of a dummy object. I have not found a good reason why a dummy variable is used, but since my queue is public and can be accessed by other classes, I think locking the queue is safer.
 
Last edited:

KB

Diamond Member
Nov 8, 1999
5,406
389
126
First I would recommend against using datatables. You can search google for pages upon pages of discussions about using objects lists vs datatables. But if you want to use a DataTable, I would recommend you create a typed dataset and add the typed table to the dataset. Then you can drop and drop that typed dataset from the Data Sources window, in Visual Studio, onto the form. This action automaticaly creates the bindingsource, datasource and a grid with the respective columns. It saves a lot of time and typed datasets have compile time type-checking.

The threading problem you are having is because you can only update the UI on the UI thread. You will have to do somethign like: http://msdn.microsoft.com/en-us/magazine/cc188732.aspx
 

elkinm

Platinum Member
Jun 9, 2001
2,146
0
71
Since I am having threading issues, can I just reset the binding from the table thread itself?

I am thinking that I don't reset the bidnings from the logging thread, but instead have a timer in Form2 that will periodically resetbinding on it's own.
If this is possible, it would need to check for locks, before resetting, which means I will need another queue to ensure the main thread cannot be blocked from writing to the queue.
 

elkinm

Platinum Member
Jun 9, 2001
2,146
0
71
First I would recommend against using datatables. You can search google for pages upon pages of discussions about using objects lists vs datatables. But if you want to use a DataTable, I would recommend you create a typed dataset and add the typed table to the dataset. Then you can drop and drop that typed dataset from the Data Sources window, in Visual Studio, onto the form. This action automaticaly creates the bindingsource, datasource and a grid with the respective columns. It saves a lot of time and typed datasets have compile time type-checking.

The threading problem you are having is because you can only update the UI on the UI thread. You will have to do somethign like: http://msdn.microsoft.com/en-us/magazine/cc188732.aspx

Can you point me to some dataset examples? The only reason I have a datatable, it is the first and only simple data type I found that I could easily bind to the datagridview of Form2. I always though it was inefficient, but by comparison, it is several orders of magnitude faster than the datagridview updating and re-drawing on every table update which is why I am trying to suspend the data binding.

I do use Delegates and BeginInvoke in my project, but my goal with the DataSource is to not touch the Form2 thread.
If Form2 is bound to bindingsource2, like this:
table -----> BidningSource1 ------> BindingSource2 -----> DataGridView.
I would think stopping and resetting binding on Bindignsource1 would not touch the second thread.
Basically I don't know or understand exactly what the ResetBindings command does and what it needs to be called on. If I only need to call in on the Form2 thread then I can definitely use a BeginInvoke or even an Invoke for that. But I think the ResetBindings command runs on all components of the bindings which exist in 2 threads and I think that is the problem?

Thanks again.
 

elkinm

Platinum Member
Jun 9, 2001
2,146
0
71
Cool, searching for List vs DataTable threads I found this: http://dotnetperls.com/datatable
This is the original page I found some time ago that had a full example of how to build a datatable and how to bind a source to it.
This is the only reason I have a datatable. I have not found complete list examples of this form and been unable to create one bound to my datagrid.
I am still looking.
 

elkinm

Platinum Member
Jun 9, 2001
2,146
0
71
I just love answering my own questions.
I added a new ResetBindings delegate in Form2, which just calls ResetBidnings on mySource from above.

In the publishing thread I replaced tableSource1.ResetBindings(false) with:
Code:
if (GridThread != null && GridThread.IsAlive)
{
      GridForm.BeginInvoke(GridForm.resetBindingDelegate, null)
}
else
{
      tableSource1.ResetBindings(false)
}
I always need to reset binding so if the GridThread is alive I invoke otherwise I reset directly and it works, at least for now.

Thanks
 

Oyster

Member
Nov 20, 2008
151
0
0
First I would recommend against using datatables...

DataTable is the closest wrapper around the native ADO APIs. Accordingly, it is one of the fastest, if not the fastest, performing entities in the ADO.NET realm. DataSets (and their derivatives like typed DataSets) are XML-based; meaning, they're extremely verbose when it comes to representing data. Working in DataTables and DataViews gives you much finer control. So if you don't mind the archaic nature of the API, there is no reason not to use DataTables and DataViews (that includes offline data manipulation based around objects like SqlDataAdapter).


Notice that I am locking on the que objects themselves instead of a dummy object.

This is fine as long as you're keeping track of the two objects. Ultimately, most programmers use global, static objects because it makes it easier to track one object [within a given AppDomain] than individual objects. Depending on how you declared your que objects, your critical code sections may not really be thread-safe. I saw a couple of other things in your code that I think can be improved (see comments):


Code:
        // SEE ME - I don't know how you're handling the exceptions, but if the Thread is being invoked using this method directly, I don't see how your main form will  be notified if this method blows up!?
        public void LogLoop()
        {
            DateTime now = DateTime.Now;
            string time = now.ToString("HH.mm.ss");
            string date = now.ToString("yyyyddMM");
            string appPath = Path.GetDirectoryName(Application.ExecutablePath);
            string dirPath = appPath + "\\Logs";
            if (!Directory.Exists(dirPath))
            {
                Directory.CreateDirectory(dirPath);
            }
            string newSWName = "Tutorial Log " + date + "-" + time + ".log";
            newSWName = dirPath + "\\" + newSWName;
            
            // SEE ME - Dispose this sucker?
            sw = new StreamWriter(newSWName, true);
            sw.AutoFlush = true;
            //parentForm.table.Rows.Add(time, Action);
            sw.WriteLine("DateTime,Action");
            while (Logging)
            {
                if (que2.Count > 0)
                {
                    // SEE ME - Simply use the "lock" keyword here - it inserts the Monitor method calls automagically, and is easier on the eyes
                    if (Monitor.TryEnter(que2))
                    {
                        try
                        {
                            //tableSource1.RaiseListChangedEvents = false;
                            while (que2.Count > 0)
                            {
                                // SEE ME - You may want to Peek() first, finish your operation, and then DeQueue. Your que2 object will be "tainted" if any of the following actions throw an exception.
                                Array e = que2.Dequeue();
                                string Date = ((DateTime)e.GetValue(0)).ToString("HH:mm:ss.fff");
                                string Action = (String)e.GetValue(1);
                                sw.WriteLine(Date + "," + Action);
                                table.Rows.Add((DateTime)e.GetValue(0), Action);
                            }
                        }
                        finally
                        {
                            Monitor.Exit(que2);
                            if (que1.Count > 0)
                            {
                                if (Monitor.TryEnter(que1))
                                {
                                    try
                                    {
                                        while (que1.Count > 0)
                                        {
                                            Array e = que1.Dequeue();
                                            string Date = ((DateTime)e.GetValue(0)).ToString("HH:mm:ss.fff");
                                            string Action = (String)e.GetValue(1);
                                            sw.WriteLine(Date + "," + Action);
                                            table.Rows.Add((DateTime)e.GetValue(0), Action);
                                        }
                                    }
                                    finally
                                    {
                                        Monitor.Exit(que1);
                                    }
                                }
                            }
                            //tableSource1.RaiseListChangedEvents = true;
                            //tableSource1.ResetBindings(false);
                        }
                    }
                }
                else


                {
                    if (que1.Count > 0)
                    {
                        if (Monitor.TryEnter(que1))
                        {
                            try
                            {
                                //tableSource1.RaiseListChangedEvents = false;
                                while (que1.Count > 0)
                                {
                                    Array e = que1.Dequeue();
                                    string Date = ((DateTime)e.GetValue(0)).ToString("HH:mm:ss.fff");
                                    string Action = (String)e.GetValue(1);
                                    sw.WriteLine(Date + "," + Action);
                                    table.Rows.Add((DateTime)e.GetValue(0), Action);
                                }
                            }
                            finally
                            {
                                Monitor.Exit(que1);
                                //tableSource1.RaiseListChangedEvents = true;
                                //tableSource1.ResetBindings(false);
                            }
                        }
                    }
                }

                Thread.Sleep(50);
            }
        }
 

elkinm

Platinum Member
Jun 9, 2001
2,146
0
71
SEE ME 2 at the SW creation. Are you referring to using a using block for the streamwriter, something I have often seen in samples, but was never sure of how tu use it. Do I just wrap everything from the sw creation to the end with a using block?

SEE ME 3 for lock. The reason I use Monitor.TryEnter as it is non-blocking and techincly I don't care if the logging thread is locked, but if it locks a resource, the main thread writing to the queue would be blocked so it goes to the backup queue.
If the log thread is in queue1 the main thread will go to que2. Que1 is locked only when the log thread is reading from it.
If the log thread sees que2 is locked, it means that the main thread is writing so it will sleep, it cannot access que1 until que2 is empty.
I know I can just lock at que2 and have the log thread wait until it is available, I just think it is cleaner on more consistent for me to have the thread run and sleep until it can work instead of waiting on a lock.

SEE ME 4 at the DeQueue. I think you are referring that since I deque first and I fail entering the data I will loose that one line. I always through of that and was surprised that examples of this online did this. I could look at the last value in the queue record it and then deque it. Is there a possibility of it failing at the deque method itself so it might write 1 line twice? Either way a missing or extra line is the least of my worries at the moment, but I will change it when I have a chance.

SEE ME 1 for exception handling for this would be at the global appdomain and thread exception handling. I have it record the error and exit the application. These are both new for me so I don't know exactly what to expect so shutting down is always safer than trying to handle it for now.

For the locking, I still don't understand. The que1 and que2 are public objects in the Form1 class. Now I can create a set of dummy objects to lock in Form1 as well (online examples use private dummy objects for some reason). The ques can be locked and worked on from outside the Form1 class so they need to be public. But with dummy variable, isn't it possible that if I forget to lock to the dummy variable I can still work on the ques but if I forget to check or lock on the ques and try to work on them when they are locked it will create an exception and at least reveal the error.
Am I understanding this correctly, and if so what am I missing that would make dummy variable better for me?

Thanks again.

EDIT: I wrapped everything from the creation of the new sw to right after the wile loop end. If it ever stops the SteamWriter will be disposed off.
 
Last edited:

Oyster

Member
Nov 20, 2008
151
0
0
SEE ME 3: All I was saying was use the "lock" keyword.

So instead of the following:
Code:
if (Monitor.TryEnter(que2))
{
    // Critical section
    try
    { }
    finally
    {
        Monitor.Exit(que2);
    }
}

Do this:
Code:
lock (que2)
{
    // Critical section
}

Both code blocks are equivalent, just that the one with the "lock" statement is easier to decipher. When using the "lock" keyword, the .NET Framework automatically generates the Monitor statements for you at compile time.


Regarding locking around a global, static object - I'll try to keep this simple, but there are a lot of other scenarios where the following example may not apply. Let's say you have a counter which must be kept thread safe. Also, let's say there are two methods Increment and Decrement which are being invoked on separate threads within the same AppDomain.

Here is a common scenario that I have seen on numerous occasions - in this case, the example is simple, but when you're dealing with multiple classes and thousands on lines of code, it is easier to lose track of your variables:
Code:
        Queue<String> que1 = new Queue<String>();
        Queue<String> que2 = new Queue<String>();
        int counter = 0;

        public void Increment()
        {
            // Out of sync
            lock (que1)
            {
                counter++;
            }
        }

        public void Decrement()
        {
            // Out of sync
            lock (que2)
            {
                counter--;
            }
        }
In the above example, the counter variable isn't being accessed in a thread-safe manner because the programmer (often inadvertently) is synchronizing around two mutually exclusive objects. This is similar to what you have in your code (though, as mentioned earlier, your code may be OK because I don't exactly know the logic around your critical code blocks).

If the programmer were to use a static, global object instead, the situation could be avoided:
Code:
        static object syncLock = new object();
        int counter = 0;

        public void Increment()
        {
            // In sync
            lock (syncLock)
            {
                counter++;
            }
        }

        public void Decrement()
        {
            // In sync
            lock (syncLock)
            {
                counter--;
            }
        }

Here, access to the critical code blocks is thread-safe. Hope this helps. Again, please note that the above example is highly subjective... please don't generalize for all threading scenarios. Ultimately, also keep in mind that this applies to threads within the same AppDomain.
 

elkinm

Platinum Member
Jun 9, 2001
2,146
0
71
I understand what you mean but that is not what I would do. I would do something like this.

Code:
int counter1 = 0;
int counter2 = 0;
        public void Increment1()
        {
            lock (counter1)
            {
                counter1++;
            }
        }
        public void Decrement1()
        {
            lock (counter1)
            {
                counter1--;
            }
        }
        public void Increment1()
        {
            lock (counter2)
            {
                counter2++;
            }
        }
        public void Decrement2()
        {
            lock (counter2)
            {
                counter2--;
            }
        }

So I lock counter1 and work on counter1 same with counter2. If I had another rogue method that did not check locks, what would happen in both cased if it would run:

Code:
        public void DecrementOLD()
        {
              counter2--;
        }

If this method somewhere in the code would try to write to counter2 while it was locked, would it cause an exception? Because with a dummy lock it would just change the counter in a very un thread-safe way.

Also, Lock is the same as Monitor.Enter(). Monitor.TryEnter() is a non-blocking attempt to get the lock, if the lock is unavailable, ie. in use by another thread, it can do something other than just wait.

I need a way to increment and decrement counter1 in such a way that no thread will ever wait for the lock. So my code would be like:
Code:
int counter1 = 0;
int counter2 = 0;
        public void Increment1()
        {
            If(Modify.TryEnter(counter1))
            {
                if (counter2 != 0)
                {
                    counter1 = counter1 - 1 + counter2;
                    counter2 = 0;
                }
                else
                {
                    counter1++;
                }
            }
            else
            {
                counter2++;
            }
        }
        public void Decrement1()
        {
            If(Modify.TryEnter(counter1))
            {
                if (counter2 != 0)
                {
                    counter1 = counter1 - 1 + counter2;
                    counter2 = 0;
                }
                else
                {
                    counter1++;
                }
            }
            else
            {
                counter2--;
            }
        }

The idea here is if the variable is locked it increments or decrements a backup variable and when the main variable is available it adds the value of counter2 to it and resets counter2.
Certainly this code is not safe itself because I neglected to out in Monitor.Exit() and I have not locked counter2 and have not done anything to ensure that the code cannot be blocked by counter2. So I would need more locks and more backup counters to ensure no primary thread is blocked.
Isn't it amazing how complicated simple increment and decrement operations are if they must be thread safe and non-blocking.

In my code I never look and two counters at once like in the above code so the main thread is never blocked and the log thread can only lock one counter at a time.
 
Last edited:

Oyster

Member
Nov 20, 2008
151
0
0
As I said, my example was highly subjective. Just to point out something, never lock on the "counter" - it is a value type. Another reason people use static reference types for synchronization :).

In skimming the code, I didn't see "Monitor.TryEnter()" - simply assumed it was Monitor.Enter(). Thanks for pointing out.

Regarding your question about an exception being thrown: I have never experienced it, but as far as my personal experience goes, the variable's value will be, simply put, in a tainted state. That is, you can't deterministically tell what the variable's state will be at a given point in time. I believe, in your example it will most definitely be in a phantom state because you're trying to synchronize around a value-type (which is not really synchronizing).

EDIT: VS 2010 prevents you from using value types within the lock statement - it throws an exception at compile-time. Not sure what the behavior is for VS 2005/2008.
 
Last edited:

elkinm

Platinum Member
Jun 9, 2001
2,146
0
71
Thanks, didn't know that I could not lock on value types.

I also did find this site: http://haacked.com/archive/2005/04/12/neverlockthis.aspx

It mentions why you cannot lock on a reference and work on that same reference inside the lock. Because with the lock it creates a copy of that object in memory for the lock so in my case locking on the queue wastes a lot of memory. Am I correct in my understanding?

Thanks again.
 

Oyster

Member
Nov 20, 2008
151
0
0
The blogpost is simply illustrating a scenario where in a deadlock results. The author doesn't claim you can't use use the instance of a reference type for synchronization. Also, your theory of "wasting a lot of memory" is over-engineered :). As long as the instance of your reference type is private, it is OK to synchronize around that object.

Again, to emphasize and to keep things simple, I'd say stick to the lock keyword. And lock around a static readonly object. It will save you a lot of trouble. There are times when you may want to use TryEnter, but I have never really felt the need for it: remember one of the golden rules of multithreading: your critical code blocks need to be super-efficient. If you're waiting for an eternity to acquire a lock on an object, hence, warranting the use of TryEnter, you need to revisit your design and logic for multithreading.

As a side note (and MS emphasizes this, too, now) - avoid using "lock (this)" and "lock (typeof(myObject))". This is well documented on MSDN - http://msdn.microsoft.com/en-us/library/c5kehkcz&#37;28v=vs.80).aspx

Here is an awesome .NET multithreading primer: http://www.albahari.com/threading/.
 

elkinm

Platinum Member
Jun 9, 2001
2,146
0
71
This is an example of exactly what I need to do: http://stackoverflow.com/questions/1300199/c-anyway-to-detect-if-an-object-is-locked
In the sense that writer to the queue for logging and actual logging thread are not in the same class.

Writing to the queue has to be non-blocking, hence the TryEnter, the logging thread itself can wait, it just needs to be sure to log in order, why it first checks if queue2 is empty before continuing. This thread could just as easily be handled with a Lock statement.

The first answer includes:
However, the problem here is that you need to make sure that the list that you are trying to synchronize access to is being locked on itself in order to synchronize access.

It's generally bad practice to use the object that access is being synchronized as the object to lock on (exposing too much of the internal details of an object)

Of course I don't understand what he means by exposing to much detail, be he does mention the need to lock the object itself.