How can I create an object of a class by using its string name instead of creating it directly? By using Activator.CreateInstance as demonstrated in this article. This will be a fairly lengthy article, but it describes some important aspects of Object Oriented Programming that sometimes programmers coming from a procedural programming mindset will completely miss in moving to an OOP language.
One of the hot topics in Object Oriented Programming (OOP) is decoupling or keeping objects loosely coupled. The less tightly coupled an application is, the easier it will be to maintain. When you do have to make changes to a loosely coupled application, the changes will normally be confined to the area you are changing. Conversely, when you change a tightly coupled application the risk of breaking something that you never intended to touch is alwasy greater.
I wrote an application recently where I used an Abstract Factory Design Pattern to create objects. If I had only a half dozen or so different objects of the same type to create, I might normally have used an if else or switch construct in C# (Select Case in VB.NET) to determine which concrete object to create based on input data. This application had approximately 40 objects of the same type (all implemented an Interface) and I did not want to convolute the code with such a construct. A 40 case switch would be a little much to say the least. So, knowing all of the input types that I would encounter and their associated processing class, I used the approach described in this article.
In a previous article on generating numerous objects of like type using a Macro, I discussed the creation of multiple classes from tables in a Word document. In this article, I will describe how I created concrete objects from those classes utilizing a dictionary without the application ever knowing which object was being created. The objects were subsequently stored in a Generic List for later processing. Since the objects were to be created and and saved in a dictionary and processed as objects of the same base type, I had to add the implementation of an interface to the classes shown in the previous article. In the article, I left that part out of the Macro and its associated discussion because that article was about code generation and I did not address the use of the resultant classes.
In this particular application, a simple description of the process was to read the input data lines, use the data from the various types of input lines to populate data objects, call the AddXMLValues method of each object, pass the data object through an XML building component, and finally use the resultant XML to call a Web Service. First, I will start with the Interface that was implemented by all of the objects which allows me to create all objects as the same type. Figure 1 shows the simple interface that all data objects implement.
Figure 1 - IProcess Interface
| namespace TestCreateInstance { interface IProcess { void Parse; void AddXMLValues(XMLDataObject xdo); } } |
| using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; namespace TestCreateInstance { public class ProcessObjectFactory { private static Dictionary<string, string> txDict; /// <summary> /// This method is called once to setup the data objects /// and the associated transaction code that triggers their /// creation. The dictionary will contain a name value pair /// which consists of the transaction id and the associated /// string name of the class that knows how to process the /// transaction type. /// This method will only add a few transactions and classes /// to the dictionary; just enough to illustrate the process. /// public static void SetupDictionary() { txDict = new Dictionary<string, string>(); txDict.Add("Name", "NameLine"); txDict.Add("Addr1", "Address1Line2"); txDict.Add("Addr2", "AddressLine2"); txDict.Add("1234", "ProductType1234"); } // method: SetupDictionary /// <summary> /// This is the factory method. The parameter txType /// is one of the four shown above plus any others that /// would be added in the real application. /// /// /// <returns> public static void CreateObject(string inputLine) { string txType = inputLine.Substring(3, 6).Trim(); // test for the existence of the transaction type // as a key in the dictionary before attempting to // reference it to prevent throwing an unexpected exception if (txDict.ContainsKey(txType)) { // the string name must be fully qualified for GetType to work string objName = "TestCreateInstance." + txDict[txType]; IProcess txObject = (IProcess)Activator.CreateInstance(Type.GetType(objName)); txObject.Parse(inputLine); if (DataObjects.objList == null) DataObjects.objList = new List<IProcess>(); DataObjects.objList.Add(txObject); return; } // if throw new NullReferenceException(); } // method: CreateObject } // class } |
| using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TestCreateInstance { public class NameLine : IProcess { #region Class Constructor // calling the constructor will populate the object public NameLine() { } #endregion // constructor #region private methods private string GetField(string dataLine, int stChar, int len) { if (dataLine.Length < 74) dataLine += Space(74 - dataLine.Length); return dataLine.Substring(stChar - 1, len).Trim(); } // Replacement for VB.NET Space Function public string Space(int spaceCount) { return String.Empty.PadLeft(spaceCount); } // method: Space #endregion // private methods #region public properties private string RecordId { get; set; } private string RecordType { get; set; } private string LastName { get; set; } private string MiddleInitial { get; set; } private string FirstName { get; set; } #endregion // public properties #region public methods public void Parse(string line) { RecordId = GetField(line, 1, 3); RecordType = GetField(line, 4, 6); FirstName = GetField(line, 10, 25); MiddleInitial = GetField(line, 35, 15); LastName = GetField(line, 50, 25); } /// <summary> /// This method will be visited so that it can /// add the values to the XMLData object that /// it knows about. /// /// public void AddXMLValues(TestCreateInstance.XMLData xdo) { xdo.LastName = this.LastName; xdo.MiddleName = this.MiddleInitial; xdo.FirstName = this.FirstName; } // method: AddXMLValues #endregion // public methods } } |
| using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TestCreateInstance { /// <summary> /// This is a dummy class /// public class XMLData { public string LastName { get; set; } public string FirstName { get; set; } public string MiddleName { get; set; } public string Address1 { get; set; } public string Address2 { get; set; } public string City { get; set; } public string State { get; set; } public string Zip { get; set; } } // class } |
| using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TestCreateInstance { public class DataObjects { public static List<IProcess> objList; /// <summary> /// Visit all of the objects in the list. This method /// is agnostic as to what will be done by the call /// to the AddXMLValues method and doesn't care. Every /// object is responsible to do its own thing so it can /// be changed without chaning any other part of the app. /// public static void ProcessAllObjects(XMLData xdo) { foreach (IProcess obj in objList) { obj.AddXMLValues(xdo); } // foreach } // method: Process } } |
| using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; namespace TestCreateInstance { class Program { static void Main(string[] args) { string data = BuildDataString(); // this would normally read an input file, but is just // going to read one line of data with a string reader // for the demo using (StringReader sr = new StringReader(data)) { ProcessObjectFactory.SetupDictionary(); string inputLine = string.Empty; while(inputLine != null) { inputLine = sr.ReadLine(); if (inputLine == null) break; // CreateObject will create an object to process // the associated transaction type and insert into a // list of objects for future processing ProcessObjectFactory.CreateObject(inputLine); } // while sr.Close(); } // using XMLData xd = new XMLData(); DataObjects.ProcessAllObjects(xd); } /// <summary> /// Build a test data string. /// /// <returns> private static string BuildDataString() { return "001Name Joe E Brown"; } // method: BuildDataString } } |
| Ask a Question, or give your feedback on my articles or products by going to the KnowDotNet Forum or by clicking on My Blog. | ![]() |