Abhishek Siddhu
Abhishek Siddhu

Reputation: 33

Calling a function on Type Parameter in Generic Class C#

How can we call a function that is defined abstract in a generic base class.

I have a generic

class Class1<T> where T : class, new()

and multiple classes which derive from it like

Class2: Class1<Class2> 
Class3: Class1<Class3>

The generic class has 3 functions

1-> accept a dynamic object and puts all the values to corresponding properties in the object of derive

2-> accepts the ID, looks for the corresponding row in database pass the dynamic object to func1 and return the result

3-> a listall function which returns all rows in table

Here is the generic code

public abstract partial class Class1<T> where T : class, new()
{
    public static EntityLayout EntityLayout { get; protected set; }

    [TypeAttributes(TypeAttributes.Options.IsPrimary, TypeAttributes.Options.IsAutoIncrement)]
    /// <summary> Automatically Incremented 64 bit Integer Primary Key
    /// represents the Unique ID of each row in Table </summary>
    public long ID { get; set; }
    /// <summary> Converts the row returned from Database to Object </summary>
    /// <param name="row"></param>
    /// <returns></returns>
    public abstract T GetDetails(dynamic row);
    public static T GetDetails(long ID)
    {
        var row = Shared.SessionWrapper.Current.globaldbcon.QuerySingle("SELECT * FROM [" 
            + EntityLayout.ContainerName + "].["
            + EntityLayout.TableName + "] WHERE ID=@0", ID);
        if (row != null) return GetDetails(row);
        return new T();
    }
    public static List<T> ListAll()
    {
        List<T> result = new List<T>();
        foreach (var row in Shared.SessionWrapper.Current.globaldbcon.Query("SELECT * FROM [" 
            + EntityLayout.ContainerName + "].["
            + EntityLayout.TableName + "]")) result.Add(GetDetails(row));
        return result;
    }
}

An example class Implementation

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Arinsys.Database;

namespace WebApplication1.Models
{
    [EntityAttributes(EntityAttributes.Options.TestingEnabled)]
    public class Class3 : Class1<Class3>
    {
        static Class3()
        {
            EntityLayout.DisplayName = "Users";
        }
        /// <summary> User ID of the User </summary>
        public long UID { get; set; }
        /// <summary> User ID of the User if defined in Universal Data Store  </summary>
        public long UDSID { get; set; }
        /// <summary> Login ID of User </summary>
        public string LoginID { get; set; }
        /// <summary> Registered email of the user. If not set will be set same as LoginID </summary>
        public string Registeredemail { get; set; }
        [TypeAttributes(TypeAttributes.Options.IsPassword)]
        /// <summary> Password of user </summary>
        public string Password { get; set; }
        /// <summary> A Unique Security Stamp used for activation/deactivation of account or similar intense tasks </summary>
        public string SecurityStamp { get; set; }
        /// <summary> Timezone ID of the Default Timezone of User </summary>
        public string DefaultTimezone { get; set; }
        /// <summary> Current Status of User </summary>
        public string CurrentStatus { get; set; }
        /// <summary> Discriminator which defines the type of user in multi-user heirarchy scenario </summary>
        public string UserType { get; set; }
        /// <summary> Number of failed login attempts in total or same session depending upon configuration. Resets after Successful Login </summary>
        public short FailedAttempts { get; set; }
        /// <summary> Date Time of Last Failed Login Attempt in UTC </summary>
        public DateTime LastFailedAttempt { get; set; }
        /// <summary> Date Time of Last Successful Login in UTC </summary>
        public DateTime LastLogin { get; set; }
        /// <summary> Creation Date of User Account in UTC </summary>
        public DateTime CreationDate { get; set; }
        public override Class3 GetDetails(dynamic row)
        {
            Class3 result = new Class3();
            if (row != null)
            {
                result.ID = Convert.ToInt64(row.ID);
                result.UID = Convert.ToInt64(row.UID);
                result.UDSID = Convert.ToInt64(row.UDSID);
                result.UserType = row.UserType;
                result.LoginID = row.LoginID;
                result.Password = row.Password;
                result.Registeredemail = row.Registeredemail;
                result.SecurityStamp = row.SecurityStamp;
                result.DefaultTimezone = row.DefaultTimezone;
                result.CurrentStatus = row.CurrentStatus;
                result.FailedAttempts = Convert.ToInt16(row.FailedAttempts);
                result.LastFailedAttempt = Convert.ToDateTime(row.LastFailedAttempt);
                result.LastLogin = Convert.ToDateTime(row.LastLogin);
                result.CreationDate = Convert.ToDateTime(row.CreationDate);
            }
            return result;
        }
    }
}

Its been two weeks searching for the answer everywhere before posting, but couldn't find the solution.

All i want is that ListAll function should call 1st function. Since it's defined abstract i am sure the deriving class has to have an implementation (even though it might be just throw NotImplementException, but implementation is guaranteed)

I first defined the implementation of 1st function in generic class itself through reflection. Though that works, but its very slow, did performance bench-marking by starting/stopping a Stopwatch at start/end of controller action and it took approx 35 seconds for just 100 rows, so it's surely not something for production use.

Points to note

Possible Solutions i guess are closest ( but i am unable to understand how to use them in my case)

What i want to achieve is that ListAll function should call 1st function accepting a dynamic object.

Some questions which come very close are these, but none of them solves my query.

Stack Overflow Q1 Stack Overflow Q2 Stack Overflow Q3

Upvotes: 2

Views: 255

Answers (1)

Ivan Stoev
Ivan Stoev

Reputation: 205589

Looks like the design should be like this

public abstract partial class Class1<T> where T : Class1<T>, new()
{
    protected abstract void Load(dynamic row);

    private static T GetItem(dynamic row)
    {
        var item = new T();
        if (row != null)
            item.Load(row);
        return item;        
    }

    public static T GetDetails(long ID)
    {
        var row = Shared.SessionWrapper.Current.globaldbcon.QuerySingle("SELECT * FROM [" 
            + EntityLayout.ContainerName + "].["
            + EntityLayout.TableName + "] WHERE ID=@0", ID);
        return GetItem(row);
    }

    public static List<T> ListAll()
    {
        List<T> result = new List<T>();
        foreach (var row in Shared.SessionWrapper.Current.globaldbcon.Query("SELECT * FROM [" 
            + EntityLayout.ContainerName + "].["
            + EntityLayout.TableName + "]")) result.Add(GetItem(row));
        return result;
    }
}

and the sample implementation

public class Class3 : Class1<Class3>    {
{
    // ...
    protected override void Load(dynamic row)
    {
        // No need to check for null, it is enforced by the base class
        ID = Convert.ToInt64(row.ID);
        UID = Convert.ToInt64(row.UID);
        // ...
    }
}

Basically you explore the Curiously recurring template pattern supported by .NET generic class constraints (T : Class1<T>) to ensure the derived class contains the abstract Load method, while the new T() part is enforced by the new() constraint.

Upvotes: 1

Related Questions