views:

871

answers:

1

I'm writing a data access library in C#, the structure of my data model classes are as follows (within the DataAccess assembly):

public abstract class DataModel {...} (protected constructor)
public abstract class DataClass {...} (protected constructor)
public abstract class DataField {...} (protected constructor)
public class IntegerField : DataField
{
   public IntegerField(DataClass cls, string name, bool isNullable, 
                       int? defaultValue, int? minValue, int? maxValue) {...}
}
public class StringField : DataField
{
   public StringField(DataClass cls, string name, bool isNullable, 
                      string defaultValue, int maxLength) {...}
}
public class BooleanField : DataField
{
   public BooleanField(DataClass cls, string name, bool isNullable, 
                       bool? defaultValue) {...}
}
...

As you can see, each field has a different constructor.

To use this you automatically generate classes from an XML file, for example, a "Store" data model, which contains a "Customer" data class and an "Employee" data class will generate this code:

public class StoreDataModel : DataModel
{
   public CustomerDataClass Customer { get; private set;}
   public EmployeeDataClass Employee { get; private set;}
   public StoreDataModel()
   {
     Customer = new CustomerDataClass(this);
     AddClass(customer)
     Employee = new EmployeeDataClass(this);
     AddClass(employee)
   }
}
public class CustomerDataClass : DataClass
{
   public StringField FirstNameField { get; private set; }
   public StringField LastNameField { get; private set; }
   public DateField BirthDateField { get; private set; }

   public CustomerDataClass(StoreDataModel model) : base(model)
   {
      FirstNameField = new StringField(this, "FirstName", true, null, 50);
      LastNameField = new StringField(this, "LastName", true, null, 50);
      ...
   }
}
public class EmployeeDataClass : DataClass {...}

My problem with this code is that since the generated code is in a different assembly than the base data model classes, the StringField/IntegerField/... classes must have public constructors and that means anyone outside the data access assembly can create instances of them and I would like to prevent that.

Since the field classes have different constructors I can't think of a way to use reflection or generics to create the fields within the base data access assembly.

The only way I could think of was add a "Initialize" method to each field class that would include the properties of that specific fields and in the custom data class constructor do this:

   public CustomerDataClass(StoreDataModel model) : base(model)
   {
      FirstNameField = AddField<StringField>(this, "FirstName", true, null)
                              .Initialize(50);
      LastNameField = AddField<StringField>(this, "LastName", true, null)
                              .Initialize(50);
      ...
   }

This will allow me to change the field constructors to internal and won't allow anyone to create an instance of a field. The problem with this solution is that it would allow anyone to run the "Initialize" method and alter the field (this can be solved by adding a private boolean member (m_IsInitialized) that will be set to true when "Initialize" is first run, and each time "Initialize" is called, if m_IsInitialized is true, it will throw an exception, but this solution is a little ugly).

Does anyone have a better solution?

+2  A: 

Well, three options:

  • Use InternalsVisibleTo to make the contructors visible
  • Write static methods in StringField etc to create instances
  • Write protected methods in DataModel or DataClass to create field instances (so CustomerDataClass could call DataClass.CreateStringField etc)

However, I don't know whether any of these is acceptable because it's not clear to me why you don't want the field classes to have public constructors anyway.

Jon Skeet
First, the reason I don't want to put a public constructor on the field classes is so no will try to create a new field and using somewhere in the rest of the code (unlikely, but still...)- InternalsVisibleTo forces the data access assembly to know the custom data model assembly....
- static methods will allow anyone to create the fields- I though about this, but whenever I add another field class, I'll need to edit the DataClass class... it's not the cleanest way.
Upvote for the InternalsVisibleTo
Jonathan C Dickinson
"InternalsVisibleTo" doesn't help me, because the data access assembly is a common library that will be used with various projects. To use it I would have to modify the data access assembly for each new project (add another InternalsVisibleTo attribute)
You still haven't explained why you don't want other assemblies to create instances.
Jon Skeet
Oh, I don't want any user to create an instance of a field class and then try to use it with the rest of the code (the user's field class can be a non-existing field and mess up the system). I want to be certain that no one but me can create fields
Well if the code that determines how many fields are needed/created isn't under your control, then it sounds fundamentally impossible to me.
Jon Skeet
A protected method within DataModel/DataClass would make it hard to do *accidentally* of course.
Jon Skeet