tags:

views:

75

answers:

2

Does anyone know of a good tutorial (C# preferred) for creating a component's hierarchical object-model for .NET 3.0 or later i.e making full use of generics?

  • work with and expose an indefinite number of contained objects;
  • prevent client applications from directly instantiating contained objects;
  • allow the client code to use 'tunnelling' style e.g.

string columnName = db.Tables["Customers"].Columns["customer_number"].Name;

I found this article, Component Object-Model Recommendations but because it is for Visual Studio .NET 2003 but I'm thinking there are better ways in .NET 3.0.

Thanks.

UPDATE:

If I've understood indexers correctly...

In the db class, I could use a SortedList as container for Table and use and indexer to access to return a Table instance. Similarly, Table's indexer would access a Column instance. So the client code could look like this:

string tableName = db["Customers"].Name;

The problem is, the db class exposes other collections in addition to Tables (StoredProcs, DBViews, etc). So I'm thinking I want to retain collection classes for each, exposing them as properties rather than using an indexer. Does this sound right? Should I use indexers on these collection classes, though?

+1  A: 

Read up on using indexers in the C# language. Here's a relevant page.

Hans Passant
@Hans Passant: I've edited my question in the hope of clarifying what indexers may or may not imply. Thanks.
onedaywhen
Yes, you got it. There is no problem, you'd simply use, say, db.StoredProcs["blah"] just as you would with db.Tables[]. The indexer is on the StoredProcs collection. Automatic if you use a .NET collection class.
Hans Passant
+1  A: 
  • allow the client code to use 'tunnelling' style e.g.

    string columnName = db.Tables["Customers"].Columns["customer_number"].Name;

[...] I want to retain collection classes for each, exposing them as properties rather than using an indexer. Does this sound right?

Yes, this sounds better than indexers, because it allows for better type safety. If you expose a normal collection, each of them can have its own element type. With an indexer, you'd be forced to choose the same collection type for all exposed collections. To illustrate this fact:

public DataTable this[string tableName] { ... }
//     ^^^^^^^^^
//     choose one, unspecific type for all exposed collections

vs.

public IList<Customer> Customers { get { ... } }
public IList<Supplier> Suppliers { get { ... } }
//           ^^^^^^^^
//           the right type for each collection

So yes, do expose single "typed" collections instead of an "untyped" collection of collections.

Side note: You say you want to allow tunnelling; some people might tell you that this violates some commonly held OO principles, such as the Single Responsibility Principle (SRP), or Separation of Concerns (SoC).


  • prevent client applications from directly instantiating contained objects
public class Db
{
    public IList<Customer> Customers { get { ... } }
    ...
}

"Contained objects" can mean two things here:

  • Someone can replace your whole Customers collection. You simply prevent this by making the property read-only, ie. only provide a getter.

  • Someone can insert a new Customer into your db.Customers collection. You prevent this by choosing a (read-only) collection type for Customers that does not allow insertion.

For example:

private List<Customer> customers = new List<Customer>();
// ^ for class-internal use only

public IList<Customer> Customers { get { return this.customers.AsReadOnly(); } }
//                                                            ^^^^^^^^^^^^^
//                                                expose collection as read-only

  • work with and expose an indefinite number of contained objects

This again can mean two things:

  • Your db exposes an indefinite number of collections. Obviously, this won't work when you want each collection to be specifically typed. This only works when all your collections are exposed as having the same type (e.g. as DataTables, or as non-generic ICollections), and you will need to provide an indexer. That is, you make your many collections accessible through one collection that you expose via the indexer.

  • You expose a well-known and finite number of collections (e.g. Customers, Suppliers, etc.). Each of these, however, can then contain as many sub-objects as you want. I've assumed this scenario when I wrote the above parts of my answer. Advantages are better type safety and compile-time checking when you do tunnelling.

Concerning the last point:

db.Customers["John Smith"].BillingAddress

vs.

db.Tables["Customers"].Rows["John Smith"].Columns["BillingAddress"] as Address

In the first case, the compiler can check the types of properties such as Customers, BillingAddress, whereas in the second case, these properties are encoded as strings, and the compiler won't notice if you have typos in there. Also, you must indicate the correct data type yourself using type-casts.

stakx