Implementing Generics Classes and Functions In C# Programs

Today we'll have a look at how to implement generics in our programs and how to make our functions and code more robust and less prone to maintenance changes. Generics were introduced from .Net Framework 2.0. When a generic class is declared, the compiler will know it's type only at runtime when the client will execute the code. You can declare a function or class to be a generic if you need to.

Now moving towards our program. Well in this example we have two classes that are basically acting as our database for our program code, they are Employee and Customer. Each one of them has their own properties and functions (which will return a list of that particular class). These two classes are assembled under a namespace named DBClasses. The concept is quite similar to that of a WCF Service, where the client does not directly provide access to the Database whatever query is to be performed. Instead it ws through a service layer, in other words the client has to interact with the WCF service and in turn the service will interact with the database server. The flow would be something like the following.

Image 1.jpg

But in our case there will be a layer (basically a DLL having a class called "EntityFetcher" that will fetch the data on the client's request from the respective table). In our program the client selects which table data is to be fetched. Then, depending on his request, there will be a middle layer that will fetch the data from the respective table and will display it on the screen. This would be a console application so here we are prompting a user to provide input about which table data he has to view. Since there is only one EntityFetcher class which will at runtime fetch the data from the Employee or from the Customer table, that means this class must be a generic Class. Because until runtime we don't know whose data needs to be fetched. It entirely depends on the client's request.

Now moving toward the technical aspects and design view of our program:

  1. We have one Console Application through which the client will be able to view the data of the table requested by him.
  2. The requested table's data will be fetched through the Layer (EntityFetcher) which is basically a ClassLibrary named DataAccess.
  3. The DBClassses assembly will have all our tables (i.e. Employee and Customer).
  4. There is one more Class Library named DataEntity with all the classes in the DBClasses assembly. The reason to have these classes is so that the client can work upon them; for instance when the client wants to fetch the data from a respective table. Since we know now that EntityFetcher is a generic class that requires a specific Entity, the client can make use of these classes to be ed as a generic. Make sure that all the properties exist in the respective class in the DBClasses class. The concept is quite similar to that of DataContract.

Note: The client will not have direct access to the DBClasses Library that basically contains the table of our program.

The following is the source code for it:

DBClasses Assembly

Employee.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DBClasses
{
    public class Employee
    {
        public int EmpID { get; set; }
        public string Name { get; set; }
        public double Salary { get; set; }

        public static List<Employee> GetEmployeeDetails()
        {

            int[] ids = { 1, 2, 3, 4 };
            string[] names = { "Vishal", "Rahul", "Simran", "Sameena" };
            double[] sals = { 50000, 45000, 35000, 25000 };

            List<Employee> objEmployee = new List<Employee>();
            for (int i = 0; i < ids.Length; i++)
            {
                objEmployee.Add(new Employee() { EmpID = ids[i], Name = names[i], Salary = sals[i] });
            }
            return objEmployee;
        }
    }
}

Customer.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DBClasses
{
    public class Customer
    {
        public int CustID { get; set; }
        public string Name { get; set; }
        public double CreditLimit { get; set; }

        public static List<Customer> GetCustomerDetails()
        {
            int[] ids = { 1, 2, 3, 4 };
            string[] names = { "Vishal", "Rahul", "Simran", "Sameena" };
            double[] creditLimit = { 5000, 4500, 3500, 2500 };

            List<Customer> objCustomers = new List<Customer>();
            for (int i = 0; i < ids.Length; i++)
            {
                objCustomers.Add(new Customer() { CustID = ids[i], Name = names[i], CreditLimit = creditLimit[i] });
            }
            return objCustomers;
        }
    }
}

DataEntity

Employee.cs


using
System;

using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
 
namespace
DataEntity
{
    public class
Employee
    {
        public int EmpID { get; set; }
        public string Name { get; set; }
        public double Salary { get; set; }
    }

}


Note

  1. The DataEntity.Employee class has all the properties in the DBClasses.Employee class.

  2. The DataEntity.Customer class has all the properties in the DBClasses.Customer class. This is mandatory, to have all the properties of the class whose data is to be fetched.

DataAccess

EntityFetcher.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using DBClasses;

namespace DataAccess
{
    public class EntityFetcher<TEntity>
        where TEntity : new()
    {

        private Type _type = typeof(TEntity);
        private IDictionary<string, MemberInfo> _properties = new Dictionary<string, MemberInfo>();
        private List<TEntity> _lstEntity = new List<TEntity>();

        private List<IDictionary<string, object>> records = null;

        public List<TEntity> FillData()
        {
          
 MemberInfo[] members = typeof(TEntity).GetMembers(BindingFlags.Public | BindingFlags.Instance);

            Type[] types = GetAllDBClasses();
            foreach (Type type in types)
            {
                if (type.Name == _type.Name || type.FullName == _type.FullName)
                {
                    records = LoadData(type);
                    break;
                }
            }
            foreach (var row in records)
            {
                _lstEntity.Add(BuildEntity(row));
            }
            return _lstEntity;
        }

        private List<IDictionary<string, object>> LoadData(Type type)
        {
          
 var rowList = new List<IDictionary<string, object>>();
            dynamic rows = null;
            if (type.Name == "Employee")
            {
                rows = Employee.GetEmployeeDetails();
            }
            else if (type.Name == "Customer")
            {
                rows = Customer.GetCustomerDetails();
            }
            for (int i = 0; i < rows.Count; i++)
            {
                rowList.Add(ReadData(rows[i]));
            }
            return rowList;
        }

        private IDictionary<string, object> ReadData(dynamic record)
        {
          
 var row = new Dictionary<string, object>();
 
            PropertyInfo[] properties = record.GetType().GetProperties();

            foreach (var property in properties)
            {
                row.Add(property.Name, property.GetValue(record, null));
            }
            return row;
        }

        private TEntity BuildEntity(IDictionary<string, object> record)
        {
            var objEntity = new TEntity();
            foreach (var cell in record)
            {
                var key = cell.Key;
                var value = cell.Value;
                if (value == DBNull.Value) value = null;
                SetPropertyValue(objEntity, key.ToString(), value);
            }
            return objEntity;
        }

        private void SetPropertyValue(TEntity entity, string key, object value)
        {
            if (_properties.ContainsKey(key))
            {
                MemberInfo member = _properties[key];
                var entityProp = member as PropertyInfo;
                //Find the datatype of Property and convert them into specified Data type
                string proDataType = entityProp.PropertyType.FullName.ToLower();
                value = ConvertToDataType(value, proDataType);
                SetPropertyFromDictionary(_properties[key], entity, value);
                return;
            }
            //otherwise (should be first time only) reflect it out
            //first look for a writeable public property of any case
            var property = _type.GetProperty(key,
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
            if (property != null && property.CanWrite)
            {
                _properties.Add(key, property);
                //Find the datatype of Property and convert them into specified Data type
                string proDataType = property.PropertyType.FullName.ToLower();
                value = ConvertToDataType(value, proDataType);
 
                property.SetValue(entity, value, null);
                return;
            }
        }
 
        private void SetPropertyFromDictionary(MemberInfo member, TEntity entity, object value)
        {
            var property = member as PropertyInfo;
            if (property != null)
                property.SetValue(entity, value, null);
            var field = member as FieldInfo;
            if (field != null)
            {
                field.SetValue(entity, value);
                string s = field.FieldType.ToString();
            }
        }
 
        /// <summary>
        /// Convert the value to specify data type
        /// </summary>
        /// <param name="value">value to be converted</param>
        /// <param name="toDatatype">Specified Data type into which value to be converted</param>
        /// <returns></returns>
        public object ConvertToDataType(Object value, string toDatatype)
        {
            switch (toDatatype)
            {
                //Note always specify data type in case into lower case
                case "system.int32":
                    value = Convert.ToInt32(value);
                    break;
                case "system.int16":
                    value = Convert.ToInt16(value);
                    break;
                case "system.int64":
                    value = Convert.ToInt64(value);
                    break;
                case "system.long":
                    value = Convert.ToInt64(value);
                    break;
                case "system.touint16":
                    value = Convert.ToUInt16(value);
                    break;
                case "system.touint32":
                    value = Convert.ToUInt32(value);
                    break;
                case "system.touint64":
                    value = Convert.ToUInt64(value);
                    break;
                case "system.string":
                    value = Convert.ToString(value);
                    break;
                case "system.decimal":
                    value = Convert.ToDecimal(value);
                    break;
                case "system.double":
                    value = Convert.ToDouble(value);
                    break;
            }
            return value;
        }
 
        /// <summary>
        /// To Get All DB Classes under a Specific Assembly referenced by the current Assembly
        /// </summary>
        /// <returns>Type Array</returns>
        private Type[] GetAllDBClasses()
        {
            Type[] type = null;
            foreach
(AssemblyName assemblyName in Assembly.GetExecutingAssembly().GetReferencedAssemblies())
            {
                if (assemblyName.Name.Equals("DBClasses") || assemblyName.FullName.Equals("DBClasses"))
                {
                    Assembly objAssembly = Assembly.Load(assemblyName);
                    type = objAssembly.GetTypes().ToArray<Type>();
                    break;
                }
            }
            return type;
        }

ConsoleApplication1

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DataAccess;
using System.Reflection;
using Microsoft.VisualBasic;
using DataEntity;

//No DBClasses Access

namespace ConsoleApplication1
{
    enum Tables { Employee = 1, Customer = 2 };
    class Program
    {
      
 private static void DisplayData<T>(List<T> lst)
        {
            var query = from q in lst select q;
            foreach (var record in query)
            {
                Type type = typeof(T);
                PropertyInfo[] properties = type.GetProperties();
                foreach (var property in properties)
                {
                    Console.WriteLine(property.Name + ": \t" + property.GetValue(record, null));
                }
                Console.WriteLine("");
            }
        }
        static void Main(string[] args)
        {
            Tables user_Input = 0;
            user_Input = (Tables)Convert.ToInt32(Interaction.InputBox("Press\n1. Employee Table\n2. Customer", "User Input", "1", 200, 200));
            if (user_Input == Tables.Employee)
            {
              
 EntityFetcher<Employee> obj = new EntityFetcher<Employee>();
                List<Employee> lstEmployee = obj.FillData();
                DisplayData<Employee>(lstEmployee);
            }
            else if (user_Input == Tables.Customer)
            {
                EntityFetcher<Customer> obj = new EntityFetcher<Customer>();
                List<Customer> lstEmployee = obj.FillData();
                DisplayData<Customer>(lstEmployee);
            }
            Console.ReadLine();
        }
    }
}

Note: Here I'm using VisualBasic InteractionBox prompt the user but in your case you can do the way you like.

The following is the output for it.

Image 2.jpg

Image 3.jpg

If you press "1" then Employee Data would be displayed.

If you press "2" then Customer Data would be displayed.

Image 4.jpg

Hope you liked the Example. Thanks for reading.