Author Topic: Programming Advice  (Read 10248 times)

0 Members and 1 Guest are viewing this topic.

Offline sloanjh

  • Global Moderator
  • Admiral of the Fleet
  • *****
  • Posts: 2805
  • Thanked: 112 times
  • 2020 Supporter 2020 Supporter : Donate for 2020
    2021 Supporter 2021 Supporter : Donate for 2021
Re: Programming Advice
« Reply #45 on: October 22, 2010, 12:00:24 PM »
Don't know if this is relevant or not, but I have a vague recollection of having listboxes not automagically update when the list that they're bound to updates.  This was probably just a newbie mistake on my part (I don't do a lot of Forms programming), but my recollection is that I had to rebind the listbox (to the same list) whenever the contents of the list updated.

John

PS - If the above was unintelligible, it's probably because I'm misremembering the detailed names, i.e. don't waste a lot of time trying to make sense of the above if it doesn't make sense in the first place :-)
 

Offline ndkid

  • Warrant Officer, Class 1
  • *****
  • n
  • Posts: 86
  • Thanked: 4 times
Re: Programming Advice
« Reply #46 on: October 22, 2010, 12:31:45 PM »
Now the next question is how I exclude certain items from the list, or more accurately only include those matching a set of criteria, and how do I order them? Should I do the sorting and selecting in code and then link the listbox to a temporary collection?

Steve

If you want to filter and such, you have a few general paths I think you can go down, and I'm not sure I know enough about how you are structuring the project to choose one.


1) ADO. If the fleets are being stored in a data source, then you can make the filter part of the call to that datasource, so that you only get back the results you want. There are a number of methods on datasource that can do this (filter, sort, etc), or you can write a query that gets wrapped up all nice and clean by a dataadapter.

2) Dictionary of all fleets that you iterate through. The downside here, of course, is that you can't sort a dictionary, so you'd have to iterate through the entire pile of KeyValuePairs every time you want to filter.

3) Multiple lists. Much as we discussed with the differing MapObjects, you could have one main dictionary that had all fleets, and then have other collections (dictionaries or otherwise) that correspond to particular subsets. This would probably be the fastest way to handle it if you only have one or two filters (race, for example) that you need. It could even be, if you like confusing data structures:

Code: [Select]
Dictionary<Race, Dictionary<int, Fleet>>


Of those, my inclination would be to start by seeing if #1 works for you
 

Offline Steve Walmsley (OP)

  • Aurora Designer
  • Star Marshal
  • S
  • Posts: 11658
  • Thanked: 20379 times
Re: Programming Advice
« Reply #47 on: October 22, 2010, 01:19:59 PM »

If you want to filter and such, you have a few general paths I think you can go down, and I'm not sure I know enough about how you are structuring the project to choose one.


1) ADO. If the fleets are being stored in a data source, then you can make the filter part of the call to that datasource, so that you only get back the results you want. There are a number of methods on datasource that can do this (filter, sort, etc), or you can write a query that gets wrapped up all nice and clean by a dataadapter.

2) Dictionary of all fleets that you iterate through. The downside here, of course, is that you can't sort a dictionary, so you'd have to iterate through the entire pile of KeyValuePairs every time you want to filter.

3) Multiple lists. Much as we discussed with the differing MapObjects, you could have one main dictionary that had all fleets, and then have other collections (dictionaries or otherwise) that correspond to particular subsets. This would probably be the fastest way to handle it if you only have one or two filters (race, for example) that you need. It could even be, if you like confusing data structures:

Code: [Select]
Dictionary<Race, Dictionary<int, Fleet>>


Of those, my inclination would be to start by seeing if #1 works for you

One of my design goals for this project is to limit data access as much as possible, as that is the primary performance limitation in Aurora. At the moment I intend to load as much as possible at program start using TableAdapters, manipulate data in memory as much as possible and then save on program close. It sounds like what I should so is create a second collection, one that is suitable for sorting and contains only names and keys, then iterate through Fleets to fill that collection and set that collection as the datasource of the listbox. I assume that would be much faster performance-wise than a data query. As long as the keys are the same, I can use the SelectedValue returned from a selection change on the list box as a key to access data in the main collection. I know it sounds a little over-engineered, which is probably because I am still in the very early phases of learning both WPF and C#, but it should be fast and easy for me to understand :)

Steve
 

Offline ndkid

  • Warrant Officer, Class 1
  • *****
  • n
  • Posts: 86
  • Thanked: 4 times
Re: Programming Advice
« Reply #48 on: October 22, 2010, 01:35:00 PM »
One of my design goals for this project is to limit data access as much as possible, as that is the primary performance limitation in Aurora. At the moment I intend to load as much as possible at program start using TableAdapters, manipulate data in memory as much as possible and then save on program close. It sounds like what I should so is create a second collection, one that is suitable for sorting and contains only names and keys, then iterate through Fleets to fill that collection and set that collection as the datasource of the listbox. I assume that would be much faster performance-wise than a data query. As long as the keys are the same, I can use the SelectedValue returned from a selection change on the list box as a key to access data in the main collection. I know it sounds a little over-engineered, which is probably because I am still in the very early phases of learning both WPF and C#, but it should be fast and easy for me to understand :)

You may well be right about the performance. I know that, for the enterprise applications I usually am writing code for, there tend to be long discussions about when we ought to do things in memory versus when we ought to let the database do them. It's definitely scenario-dependent. And, of course, we're often using fairly beefy machines to do our DB work, we're making sure our DBs are indexed and tuned, etc, which is a far cry from "I've got an access DB sitting here". :-)

However, one thing I would note is that "datasource" isn't inherently synonymous with "stored on disk". You can have a completely in-memory datasource. You could, in fact, load your fleet table from access into an in-memory data source, and still access that data source with DataAdapters. It might be worth seeing if that pattern is faster or slower than just filtering yourself... I'd imagine it'll depend at least in part on what sort of filters you're applying.
 

Offline sloanjh

  • Global Moderator
  • Admiral of the Fleet
  • *****
  • Posts: 2805
  • Thanked: 112 times
  • 2020 Supporter 2020 Supporter : Donate for 2020
    2021 Supporter 2021 Supporter : Donate for 2021
Re: Programming Advice
« Reply #49 on: October 22, 2010, 08:18:25 PM »
However, one thing I would note is that "datasource" isn't inherently synonymous with "stored on disk". You can have a completely in-memory datasource. You could, in fact, load your fleet table from access into an in-memory data source, and still access that data source with DataAdapters. It might be worth seeing if that pattern is faster or slower than just filtering yourself... I'd imagine it'll depend at least in part on what sort of filters you're applying.
Just to pile on with this comment, you can make sql-type queries on arrays etc. in C#.  IIRC, the name for this is LINQ.  I personally haven't used it, but I'm not a DB programmer :-)  Whatever floats your boat :-)

John
 

Offline Steve Walmsley (OP)

  • Aurora Designer
  • Star Marshal
  • S
  • Posts: 11658
  • Thanked: 20379 times
Re: Programming Advice
« Reply #50 on: October 24, 2010, 06:56:04 AM »
One of my design goals for this project is to limit data access as much as possible, as that is the primary performance limitation in Aurora. At the moment I intend to load as much as possible at program start using TableAdapters, manipulate data in memory as much as possible and then save on program close. It sounds like what I should so is create a second collection, one that is suitable for sorting and contains only names and keys, then iterate through Fleets to fill that collection and set that collection as the datasource of the listbox. I assume that would be much faster performance-wise than a data query. As long as the keys are the same, I can use the SelectedValue returned from a selection change on the list box as a key to access data in the main collection. I know it sounds a little over-engineered, which is probably because I am still in the very early phases of learning both WPF and C#, but it should be fast and easy for me to understand :)

Sometimes the hardest part of learning a new language is just getting used to completely new concepts. In the above I was trying to find a way to get a key for the Fleet object selected in the listbox so I could look it up in the Fleets collection. What I had overlooked is that the WPF listbox is really just a collection of references to objects. I don't need to get a value from the listbox to look up the correct object in the Fleets collection because the Listbox SelectedItem property is a direct reference to the Fleet object anyway. All the collections involved, the Dictionary FleetsList for all Fleets, my new RaceFleets List collection for the Fleet objects of a particular race that I am using for filtering and the Listbox itself, are all just references to single set of Fleet objects on the heap. Once the Listbox is bound to the RaceFleets collection, the SelectedItem property of the Listbox is all I need because that is the relevant Fleet object.

In fact, now I am starting to get my head around C# and WPF, I probably don't even need the keyvalue pairs provided by the Dictionary object. A simple List would be sufficient as I need to be thinking in terms of object references, not a data-centric style of using Ints as keys. I should make a design decision to use object references throughout rather than database keys.

EDIT: I have gone through and replaced all Dictionary collections with List collections and that has simplied things considerably. Also, because I am now using object references, that has also removed some of the earlier complexity regarding the display of items in listboxes. I have deleted the lines of code that refer to DisplayMemberPath and SelectedValuePath because with a List collection overiding of the Fleet object ToString function is sufficient. Listboxes now display correctly and return references to Fleet Objects or StarSystem objects when items are selected. Definitely a lightbulb moment in my venture into C#/WPF.

Steve
« Last Edit: October 24, 2010, 07:24:59 AM by Steve Walmsley »
 

Offline sloanjh

  • Global Moderator
  • Admiral of the Fleet
  • *****
  • Posts: 2805
  • Thanked: 112 times
  • 2020 Supporter 2020 Supporter : Donate for 2020
    2021 Supporter 2021 Supporter : Donate for 2021
Re: Programming Advice
« Reply #51 on: October 24, 2010, 10:06:05 AM »
In fact, now I am starting to get my head around C# and WPF, I probably don't even need the keyvalue pairs provided by the Dictionary object. A simple List would be sufficient as I need to be thinking in terms of object references, not a data-centric style of using Ints as keys. I should make a design decision to use object references throughout rather than database keys.

This is a really good point - sorry I didn't think to make it earlier :-)

One place to be careful with this is when working with value-classes, which do not live on the managed heap - instead the have the same semantics as a primitive such as int.  It is very easy once you've been working with C# around that operator=() on a value-class object copies the state, rather than copying the reference, which means that changing the copy does not have side-effects on the original.  This can (and has for me) lead to subtle, nasty bugs in code when an object is changed from managed to value, or vice-versa.  This might be a good place to use the Hungarian notation espoused in that Joel article I linked to, and indicate all value-class variables with a "v" in the variable name....

The equivalent issue (value vs. managed) in C++ came up at work a couple of weeks ago - we were trying to decide whether an object in the interface should be a smart pointer or something that looked like a value-class.  We realized that the fundamental question was the following:
Code: [Select]
Foo a(3);
Foo b = a;
a.Set(7);
assert(b.Get() == 3);  // do a and b share state?

If the design of Foo is that the assertion is true for Foo objects, then state is not shared (as is the case with primitives like int) and Foo should be written as a value-class so that the person coding against it has a subliminal cue that state is not shared.  Note that this will typically happen if Foo doesn't have any non-const methods, and so can't be changed once it is created.  Conversely, if the design of Foo is that the assertion is false, then the state is shared, and Foo should be a smart pointer (which is the C++ equivalent of a C# managed class).

EDIT:  Hmmm - I think I just decided that only const classes (with no non-const public methods) should be value-classes in C#.

John

« Last Edit: October 24, 2010, 10:23:33 AM by sloanjh »
 

Offline sloanjh

  • Global Moderator
  • Admiral of the Fleet
  • *****
  • Posts: 2805
  • Thanked: 112 times
  • 2020 Supporter 2020 Supporter : Donate for 2020
    2021 Supporter 2021 Supporter : Donate for 2021
Re: Programming Advice
« Reply #52 on: October 24, 2010, 10:21:19 AM »
In fact, now I am starting to get my head around C# and WPF, I probably don't even need the keyvalue pairs provided by the Dictionary object. A simple List would be sufficient as I need to be thinking in terms of object references, not a data-centric style of using Ints as keys. I should make a design decision to use object references throughout rather than database keys.

While I think this is the right way to think of things, one of the places it's likely to get you into trouble is when you try to persist the data to disk.  At that point, you're going to need identifiers to replace the object references, so you might need integer ids anyway.  I would recommend coding up the save/restore code real soon now (assuming you don't already have it coded up) so that you don't have to retrofit it.

I suspect you'll still want to save/restore using a database, but I wanted to point out two alternatives:
1)  IIRC, there's an ISerializable interface.  I recall looking at it and not having it suite my needs, though.
2)  If you want to read/write XML, you should look at XmlSerializer (might have caps wrong) - it sets it up so you can read/write XML in roughly 5 lines of code.  The object creates its own schema at run-time, along with a parser for the schema.  So rather than parsing the XML into XML data structures in memory which you then have to use to create your class objects (like Xerces (sp?)), it creates your objects directly from the XML.  It's REALLY cool :-)  The downside is that it's not clever enough to manage circular dependencies in the data objects, so you have to create proxy objects (which own integers which are essentially tag ids in a list of objects being refered to, i.e. DB tags) that are used in the objects being xml-ized.  Since you're probably going to have circular dependencies all over the place, this would be a big burden for you.

Information overload, anyone? :-)

John
 

Offline Steve Walmsley (OP)

  • Aurora Designer
  • Star Marshal
  • S
  • Posts: 11658
  • Thanked: 20379 times
Re: Programming Advice
« Reply #53 on: October 24, 2010, 10:47:44 AM »
While I think this is the right way to think of things, one of the places it's likely to get you into trouble is when you try to persist the data to disk.  At that point, you're going to need identifiers to replace the object references, so you might need integer ids anyway.  I would recommend coding up the save/restore code real soon now (assuming you don't already have it coded up) so that you don't have to retrofit it.

I have been thinking about saving the objects. I have included the database key as part of the object definition so when I save it I can match the correct database record using ObjectReference.DatabaseKey. Although I am manipulating objects by reference, in most cases the objects still match the database tables. For example, the Universe object contains a List of StarSystems, each of which contains a List of Stars, each of which contains a List of SystemBodies. The List collections match the System, Star and SystemBody tables and I can save the records based on the SystemID, StarID and SystemBodyIDs contained in the individual objects.

Steve

« Last Edit: October 24, 2010, 10:51:54 AM by Steve Walmsley »
 

Offline sloanjh

  • Global Moderator
  • Admiral of the Fleet
  • *****
  • Posts: 2805
  • Thanked: 112 times
  • 2020 Supporter 2020 Supporter : Donate for 2020
    2021 Supporter 2021 Supporter : Donate for 2021
Re: Programming Advice
« Reply #54 on: October 24, 2010, 11:02:28 AM »
While we're on the topic of getting your head around things in C#, it might be useful to mention friendship.  In C#, there's no such thing "friend" keyword.  Note that "friend" is like a loaded gun; it's very powerful when used correctly, but can blow up in your face if used incorrectly.  The correct way to think of a friend class is as an extension of the interface of the original class (I think I got this from one of Robert Martin's excellent design books).  One has to think of the friend this way, since both the friend and the original class have access to the private data of the original.

In C#, we realized that the correct way to do this is to embed the friend class as a public (or private, if you just need a helper) member of the original.  For example:
Code: [Select]
   public partial class Foo
    {
        private List<double> _values;  // private data

        public int Count // this is a const method, since no "set"
        {
            get
            {
                return _values.Count;
            }
        }

        public double Value(int i) // const method (I couldn't remember syntax for op[])
        {
                return _values[i];
        }

        private Foo(List<double> values) // constructor is private to force construction through Builder
        {
            _values = values;
        }
    }

    public partial class Foo
    {
        public class Builder
        {
            private List<double> _bValues = new List<double>();

            public void Add(double value)
            {
                _bValues.Add(value);
            }

            public Foo Create()
            {
                return new Foo(_bValues);
            }
        }
    }
Here I've created a Foo object which is essentially a const list of doubles (I should really implement IEnumerable on it).  It comes with a Builder object, which is the only thing that can create a Foo.  By putting the Add method in the builder, I've extended the interface to allow construction of Foo objects without adding unwanted public methods to Foo.  This trick can also be used to group related classes - for example the concrete enumerator for List<T> is List<T>.Enumerator.  In other words, classes in C# can and should be thought of as mini-namespaces.

One more thing:  I used the "partial" keyword in the class definition of Foo.  That lets me put Foo.Bulder in a different file than Foo, which is useful for avoiding mega-files.

EDIT: Oops - It helps to put the code sample in the right place :-)

John
« Last Edit: October 24, 2010, 11:08:58 AM by sloanjh »
 

Offline sloanjh

  • Global Moderator
  • Admiral of the Fleet
  • *****
  • Posts: 2805
  • Thanked: 112 times
  • 2020 Supporter 2020 Supporter : Donate for 2020
    2021 Supporter 2021 Supporter : Donate for 2021
Re: Programming Advice
« Reply #55 on: October 24, 2010, 11:07:02 AM »
I have been thinking about saving the objects. I have included the database key as part of the object definition so when I save it I can match the correct database record using ObjectReference.DatabaseKey. Although I am manipulating objects by reference, in most cases the objects still match the database tables. For example, the Universe object contains a List of StarSystems, each of which contains a List of Stars, each of which contains a List of SystemBodies. The List collections match the System, Star and SystemBody tables and I can save the records based on the SystemID, StarID and SystemBodyIDs contained in the individual objects.

I suspected you would be on top of this.  I suspect the trouble you're going to have now is keeping the DB tables and the object data members the same as you change the layout of the tables.  I remember reading somewhere a long time ago that OODB design is hard and nasty, but I never understood why - this might be what they were talking about (I haven't done it myself....).

John
 

Offline Steve Walmsley (OP)

  • Aurora Designer
  • Star Marshal
  • S
  • Posts: 11658
  • Thanked: 20379 times
Re: Programming Advice
« Reply #56 on: October 24, 2010, 11:07:19 AM »
One place to be careful with this is when working with value-classes, which do not live on the managed heap - instead the have the same semantics as a primitive such as int.  It is very easy once you've been working with C# around that operator=() on a value-class object copies the state, rather than copying the reference, which means that changing the copy does not have side-effects on the original.  This can (and has for me) lead to subtle, nasty bugs in code when an object is changed from managed to value, or vice-versa.  This might be a good place to use the Hungarian notation espoused in that Joel article I linked to, and indicate all value-class variables with a "v" in the variable name....

The equivalent issue (value vs. managed) in C++ came up at work a couple of weeks ago - we were trying to decide whether an object in the interface should be a smart pointer or something that looked like a value-class.  We realized that the fundamental question was the following:
Code: [Select]
Foo a(3);
Foo b = a;
a.Set(7);
assert(b.Get() == 3);  // do a and b share state?

If the design of Foo is that the assertion is true for Foo objects, then state is not shared (as is the case with primitives like int) and Foo should be written as a value-class so that the person coding against it has a subliminal cue that state is not shared.  Note that this will typically happen if Foo doesn't have any non-const methods, and so can't be changed once it is created.  Conversely, if the design of Foo is that the assertion is false, then the state is shared, and Foo should be a smart pointer (which is the C++ equivalent of a C# managed class).

EDIT:  Hmmm - I think I just decided that only const classes (with no non-const public methods) should be value-classes in C#.


At the moment everything is being managed in user-defined objects so I don't think I am using any value-class objects on the stack. For example, all the game data is contained within a Game object, so if I want to reference the current Game Time for example I would use SelectedGame.GameTime, or once I get more advanced I would be making changes to a DateTime object that is part of the Game object using methods that include validation. The only declarations at the level of MainWindow are:

        Universe Galaxy = new Universe();
        FleetList FleetsList = new FleetList();
        RaceList RacesList = new RaceList();
        GameList GamesList = new GameList();
        Game SelectedGame;

SelectedGame is set by cboGame.SelectedItem when the user selects a game from a listbox (which is databound to a collection of Game objects).

Steve
 

Offline sloanjh

  • Global Moderator
  • Admiral of the Fleet
  • *****
  • Posts: 2805
  • Thanked: 112 times
  • 2020 Supporter 2020 Supporter : Donate for 2020
    2021 Supporter 2021 Supporter : Donate for 2021
Re: Programming Advice
« Reply #57 on: October 24, 2010, 11:12:14 AM »
At the moment everything is being managed in user-defined objects so I don't think I am using any value-class objects on the stack. For example, all the game data is contained within a Game object, so if I want to reference the current Game Time for example I would use SelectedGame.GameTime, or once I get more advanced I would be making changes to a DateTime object that is part of the Game object using methods that include validation. The only declarations at the level of MainWindow are:

        Universe Galaxy = new Universe();
        FleetList FleetsList = new FleetList();
        RaceList RacesList = new RaceList();
        GameList GamesList = new GameList();
        Game SelectedGame;

SelectedGame is set by cboGame.SelectedItem when the user selects a game from a listbox (which is databound to a collection of Game objects).

Yep - the place it burned me was on some tiny little class - I think it was some sort of identifier object.  Most of your main objects are going to be managed classes, since they're typically big, complex, and intended to be shared among references.  It's the tiny little classes that bite you :-)

John
 

Offline Steve Walmsley (OP)

  • Aurora Designer
  • Star Marshal
  • S
  • Posts: 11658
  • Thanked: 20379 times
Re: Programming Advice
« Reply #58 on: October 25, 2010, 06:25:57 AM »
Welcome to today's edition of Steve's Programming Problems...

I have a listbox (lstSystems) bound to a collection of System objects (Galaxy.SystemList). The ToString for System has been overidden to return the System Name. Everything works fine and the listbox displays a list of system names. I would like to sort the systems alphabetically in the listbox. The Items property of the listbox has a collection of SortDescription objects, which is empty at runtime. Therefore I am using the following code snippet to try and sort the listbox on SystemName.

            lstSystems.ItemsSource = Galaxy.SystemList;
            lstSystems.Items.SortDescriptions.Add(new SortDescription("SystemName", ListSortDirection.Ascending));

The sort order does change after the second line is executed and it always changes in the same way, but it certainly isn't anything to do with system name. In fact, despite the fact the sort always happens in the same way I can find no rational explanation for the order of the systems. Any ideas? Am I missing something obvious?

EDIT: Another piece of the puzzle. If I change the ListSortDirection to Descending, I still get the same 'random' order as I do with Ascending!

EDIT2: If I change the second line of code to include a nonsense field name the program compiles and runs with no problem and I still get the same weird sort order. For some reason the fieldname is being ignored.

            lstSystems.Items.SortDescriptions.Add(new SortDescription("TotallyRandomText", ListSortDirection.Ascending));

EDIT3: If I forget all of the above and sort the SystemList collection using the following (which I found but don't really understand), the listbox is sorted correctly (because the collection is sorted not the listbox view of the collection). I would still like to know why the original code doesn't work and why the new code does :)

            SystemList.Sort
                (delegate(StarSystem sys1, StarSystem sys2)
            {
                return Comparer<string>.Default.Compare
                    (sys1.SystemName, sys2.SystemName);
            });

Steve
« Last Edit: October 25, 2010, 07:56:29 AM by Steve Walmsley »
 

Offline sloanjh

  • Global Moderator
  • Admiral of the Fleet
  • *****
  • Posts: 2805
  • Thanked: 112 times
  • 2020 Supporter 2020 Supporter : Donate for 2020
    2021 Supporter 2021 Supporter : Donate for 2021
Re: Programming Advice
« Reply #59 on: October 25, 2010, 08:38:43 AM »
The sort order does change after the second line is executed and it always changes in the same way, but it certainly isn't anything to do with system name. In fact, despite the fact the sort always happens in the same way I can find no rational explanation for the order of the systems. Any ideas? Am I missing something obvious?
All I can say is that this sounds very similar to a past problem I had that I described a few days ago - I couldn't get the ListBox to refresh when the DataSource (?) changed.  Given my experience with ListBox, sorting the list sounds like a good idea, especially if you're going to be doing it a lot (naive sorting costs N^2, and even non-naive will be NlogN).
Quote
I would still like to know why the original code doesn't work and why the new code does :)

            SystemList.Sort
                (delegate(StarSystem sys1, StarSystem sys2)
            {
                return Comparer<string>.Default.Compare
                    (sys1.SystemName, sys2.SystemName);
            });

Steve
"delegate" is C#'s fancy name for "function pointer".  So you're using the built-in sort function of List<T>, and passing it a function pointer (to do the comparisons) that's the equivalent of calling strcmp on the SystemName field of the items.  Hopefully that answers the 2nd question - I haven't a clue for the first.

John