0

I have an abstract class in C#:

public abstract class DataSeed {

  private Context _context;

  private IReadOnlyList<String> _languages = new List<String> { "en", "fr" };

  private const String DRIVE = "http://mydriveurl";

}

What would be the best way to share these 3 fields only with derived classes:

public MyDataSeed : DataSeed {
}

Should I use protected, protected and turn them into properties, etc?

Note that:

  1. DRIVE is a constant and cannot be changed;
  2. _context should be initialized in the derived class constructor and should not be initialize again after it.
  3. _languages is a list that cannot be changed.
Miguel Moura
  • 36,732
  • 85
  • 259
  • 481
  • 1
    I'd just make them protected. Except for _context, which, from what you've said, I'd make a constructor parameter of the abstract class so you'll get a compiler error if you forget to initialize it. – itsme86 Mar 14 '17 at 15:10
  • 1
    What means _languages can't be changed? Do you mean it should not be possible to add items or do you mean it shouldn't be possible to do a _languages = new List()? Btw: I would make them protected, simplest ways. – user743414 Mar 14 '17 at 15:11
  • http://stackoverflow.com/questions/2149166/c-sharp-protected-property-or-field, http://stackoverflow.com/questions/3182653/are-protected-members-fields-really-that-bad, and so on. As usual: please read [ask] and share your research and make sure your question is clear for others. Read @user743414's comment. – CodeCaster Mar 14 '17 at 15:17

5 Answers5

2

I suggest something like this:

public abstract class DataSeed {
  // const, but can be used in any descendant class
  protected const String DRIVE = "http://mydriveurl";

  // _context ... should not be initialize again after it (constructor)
  protected Context Context {
    get; // in case of C# 6.0+ we can drop the set
  } 

  // _context should be initialized in the derived class constructor
  protected DataSeed(Context context) {
    if (null == context)
      throw new ArgumentNullException("context", 
        "context should be initialized in the derived class constructor");

    // C# 6.0+ feature: assigning to read only property within a costructor
    Context = context;    
  }

  // _languages is a list that cannot be changed:
  //  if you insist on list, IReadOnlyList<string> is a possible choice
  //  if "cannot be changed" dominates - IReadOnlyCollection<string>
  // static: it seems that you don't want to have _languages per each instance
  protected static readonly IReadOnlyCollection<String> _languages = 
    new List<String> { "en", "fr" }.AsReadOnly();
}

Edit: in case of C# 5.0- as Abion47 pointeed out in the comments we can ensure that _context can't be assigned but in the constructor with a help of readonly

  // _context ... should not be initialize again after it (constructor)
  private readonly Context _context;

  protected Context Context {
    get {
      return _context;
    }
  } 

  // _context should be initialized in the derived class constructor
  protected DataSeed(Context context) {
    if (null == context)
      throw new ArgumentNullException("context", 
        "context should be initialized in the derived class constructor");

    // assigning to read only field within a costructor
    _context = context;    
  }
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
  • `Context` wouldn't be able to get set from a derived class, but it's still could from the base class. I'd argue that it should be `readonly` so as to enforce that it will only get initialized once ever. – Abion47 Mar 14 '17 at 15:26
  • @Abion47: I agree, an additional protection can be enforced. In case of C# 6.0+ we can change `protected Context` into *readonly* property and still assign it within the constructor. – Dmitry Bychenko Mar 14 '17 at 15:35
  • In pre-C#6, you could still make `Context` a property getter to a `readonly` field and then assign to the field in the constructor. – Abion47 Mar 14 '17 at 15:38
1

note 1.: If all your properties and methods have a lower access scope than your class, you should consider whether or not your class should share the same scope. It's not possible to give a class protected, but an internal should suffice.

note 2. If your actual usage is of similar scope, I'd put DRIVE in a static class and turn your other 2 properties in an interface due to the answers below.

1 & 3 are enforced by your data type already, although someone can always use new to create a different property which can be changed.

2 That's up to the child class. You cannot enforce the constructor make-up from the parent class in a child class. You can only make a constructor in the parent, which the child can inherit.

CthenB
  • 800
  • 6
  • 17
0

If I understand correctly what you need I'd switch them to protected properties with a private setter for the two fields.

For the constant I'll just set it as protected so the derived classes can access it.

Zalomon
  • 553
  • 4
  • 14
0

You can use the following which will allow child classes to access the properties without allowing them to instantiate the values.

public abstract class DataSeed {

  protected Context _context { get; set; }

  protected IReadOnlyList<String> _languages  { get; private set; } = new List<String> { "en", "fr" }.AsReadOnly();

  protected const String DRIVE = "http://mydriveurl";

}
StfBln
  • 1,137
  • 6
  • 11
0

To expose the fields to derived classes, you mark them as protected rather than private. Furthermore, you can mark _context as readonly so that it can only get initialized in the constructor, and then add a protected constructor to DataSeed that the derived class calls.

Also, _languages would still be vulnerable if a derived class casts it back to a List. Instead, you should consider using a ReadOnlyCollection and marking it as readonly as well.

public abstract class DataSeed 
{
    protected const String DRIVE = "http://mydriveurl";
    protected readonly Context _context;

    protected readonly ReadOnlyCollection<string> _languages = 
                                     (new List<string> { "en", "fr" }).AsReadOnly();

    protected DataSeed()
    {
        _context = someValue;
    }
}

public MyDataSeed : DataSeed 
{
    public MyDataSeed()
        : base()
    {
        // Other constructor-y stuff
    }
}
Abion47
  • 22,211
  • 4
  • 65
  • 88