Overloaded constructors in C# similar to Delphi (with multiple names)
c#, constructor, delphi, fxcop, overloading
Solution
For starters, I would replace your nested "ElementKind" `class` with an `enum`:
public enum ElementKind
{
RegularElement,
IntParamElement,
DoubleParamElement,
LocalElement
}
In addition, I don't think your Delphi code needs to be mapped to a constructor. You would probably be better off using static factory methods that return a `DataElement`. For example:
public static DataElement Create(string name)
{
return new DataElement(ElementKind.Regular, name);
}
public static DataElement CreateParam(string name, int defaultInt);
{
return new DataElement(ElementKind.IntParam, name);
// ... use defaultInt ...
}
// similar to above
public static DataElement CreateParam(string name, double defaultDouble);
public static DataElement CreateLocal(string name);
Since you're using the factory functions to create your DataElement objects, you should make the constructors private.
You would then update your Run() function to use these as follows:
public void Run()
{
DataElement data = DataElement.Create("Element");
DataElement parI = DataElement.CreateParam("IntElement", 22);
DataElement parD = DataElement.CreateParam("DblElement", 3.14);
DataElement locl = DataElement.CreateLocal("LocalElement");
}
Update: I included the suggested changes to your `Run()` function and corrected the basic `Create()` factory method (I believe it's supposed to return a "Regular" `DataElement`).
Problem
I am trying to port some code from Delphi to C# and I have found a construction which I cannot implement in a reasonable manner, while complying with .NET Framework design guidelines (which I address at the end of my question). Obviously C#, Java, C++ (and many other languages) provide meanings of method/constructor overloading, but Delphi constructors can additionally have multiple names. This way it's possible to write code which directly represents the intent: ``` var Data, ParI, ParD, Locl: TDataElement; begin Data := TDataElement.Create('Element'); ParI := TDataElement.CreateParam('IntElement', 22); ParD := TDataElement.CreateParam('DblElement', 3.14); Locl := TDataElement.CreateLocal('LocalElement'); // ... use the above objects ... end; ``` Simplified code below: ``` unit DataManager; interface TDataElement = class FName: string; FPersistent: Boolean; public constructor Create(AName: string); constructor CreateParam(AName: string; DefaultInt: Integer); overload; constructor CreateParam(AName: string; DefaultDouble: Double); overload; constructor CreateLocal(AName: string); property Name: string read FName;; property Persistent: Boolean read FPersistent; end; implementation constructor TDataElement.Create(AName: string); begin FName := AName; FPersistent := True; // ... other initialization ... end; constructor TDataElement.CreateParam(AName: string; DefaultDouble: Double); begin Create(AName); // ... use DefaultInt ... end; constructor TDataElement.CreateParam(AName: string; DefaultInt: Integer); begin Create(AName); // ... use DefaultDouble... end; constructor TDataElement.CreateLocal(AName: string); begin Create(AName); FPersistent := False; // ... other code for local (non-persistent) elements ... end; ``` Specifically in C# constructor must have the same name as the class, so first I have tried to differentiate behavior with enumeration. Alas, I've stumbled upon several problems: - first parameter in each constructor is the same type (ElementKind) - constructors are not easily recognizable like in Delphi (Create vs. CreateParam vs. CreateLocal) - extra care is required in subclasses of DataElement - possibility of mistakes, like specifying ElementKind.DoubleParam and passing integer value - an extra bool parameter is required to handle local elements First attempt below: ``` public enum ElementKind { Regular, IntParam, DoubleParam, Local } public class DataElement { private string FName; public string Name { get { return FName; } } private bool FPersistent; public bool Persistent { get { return FPersistent; } } public DataElement(ElementKind kind, string name) { FName = name; // ugly switch :-( switch (kind) { case ElementKind.Regular: case ElementKind.IntParam: case ElementKind.DoubleParam: FPersistent = true; break; case ElementKind.Local: FPersistent = false; break; } // ... other initialization ... } public DataElement(ElementKind kind, string name, int defaultInt) : this(kind, name) { // ... use defaultInt ... } public DataElement(ElementKind kind, string name, double defaultDouble) : this(kind, name) { // ... use defaultDouble ... } // Redundant "bool local" parameter :-( public DataElement(ElementKind kind, string name, bool local) : this(kind, name) { // What to do when "local" is false ??? // ... other code for local (non-persistent) elements ... } } public class Program { public void Run() { DataElement data = new DataElement(ElementKind.Regular, "Element"); DataElement parI = new DataElement(ElementKind.IntParam, "IntElement", 22); DataElement parD = new DataElement(ElementKind.DoubleParam, "DblElement", 3.14); DataElement locl = new DataElement(ElementKind.Local, "LocalElement"); } } ``` Then I have tried more object-oriented way to differentiate constructors by types, while keeping the same initialization code in Run() method: ``` public class ElementKind { public class RegularElement { internal RegularElement() { /* disallow direct creation */ } } public class IntParamElement { internal IntParamElement() { /* disallow direct creation */ } } public class DoubleParamElement { internal DoubleParamElement() { /* disallow direct creation */ } } public class LocalElement { internal LocalElement() { /* disallow direct creation */ } } public static readonly ElementKind.RegularElement Regular = new RegularElement(); public static readonly ElementKind.IntParamElement IntParam = new IntParamElement(); public static readonly ElementKind.DoubleParamElement DoubleParam = new DoubleParamElement(); public static readonly ElementKind.LocalElement Local = new LocalElement(); } public class DataElement { private string FName; public string Name { get { return FName; } } private bool FPersistent; public bool Persistent { get { return FPersistent; } } protected DataElement(string name) { FName = name; // ... other initialization ... } public DataElement(ElementKind.RegularElement kind, string name) : this(name) { FPersistent = true; } public DataElement(ElementKind.IntParamElement kind, string name, int defaultInt) : this(name) { FPersistent = true; // ... use defaultInt ... } public DataElement(ElementKind.DoubleParamElement kind, string name, double defaultDouble) : this(name) { FPersistent = true; // ... use defaultDouble ... } public DataElement(ElementKind.LocalElement kind, string name) : this(name) { FPersistent = false; // ... other code for "local" elements ... } } public class Program { public void Run() { DataElement data = new DataElement(ElementKind.Regular, "Element"); DataElement parI = new DataElement(ElementKind.IntParam, "IntElement", 22); DataElement parD = new DataElement(ElementKind.DoubleParam, "DblElement", 3.14); DataElement locl = new DataElement(ElementKind.Local, "LocalElement"); } } ``` Everything compiles and works very well. So what is my problem here? .NET Framework design guidelines, and a tool named Microsoft FxCop. After running the last code through this tool I got multiple breaking problems (see below). And the question is: how to design my classes to comply with .NET design guidelines and best practices? Breaking - Certainty 90% - Nested types should not be visible - ElementKind+RegularElement Breaking - Certainty 90% - Nested types should not be visible - ElementKind+IntParamElement Breaking - Certainty 90% - Nested types should not be visible - ElementKind+DoubleParamElement Breaking - Certainty 90% - Nested types should not be visible - ElementKind+LocalElement Breaking - Certainty 90% - Static holder types should not have constructors - ElementKind Breaking - Certainty 75% - Identifiers should not contain type names - DataElement.#.ctor(ElementKind+IntParamElement,System.String,System.Int32) Breaking - Certainty 75% - Identifiers should not contain type names - DataElement.#.ctor(ElementKind+DoubleParamElement,System.String,System.Double) Non Breaking - Certainty 25% - Do not declare read only mutable reference types - ElementKind.#Regular Non Breaking - Certainty 25% - Do not declare read only mutable reference types - ElementKind.#IntParam Non Breaking - Certainty 25% - Do not declare read only mutable reference types - ElementKind.#DoubleParam Non Breaking - Certainty 25% - Do not declare read only mutable reference types - ElementKind.#Local