【问题标题】:Calling a function on Type Parameter in Generic Class C#在泛型类 C# 中调用类型参数上的函数
【发布时间】:2016-01-10 12:16:17
【问题描述】:

我们如何调用在泛型基类中定义为抽象的函数。

我有一个通用的

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

以及从它派生的多个类

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

泛型类有 3 个函数

1-> 接受一个动态对象,并将所有值放入派生对象中对应的属性

2-> 接受ID,在数据库中查找对应的行将动态对象传递给func1并返回结果

3-> 返回表中所有行的 listall 函数

这是通用代码

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;
    }
}

一个示例类实现

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;
        }
    }
}

在发布之前已经花了两个星期到处寻找答案,但找不到解决方案。

我想要的只是 ListAll 函数应该调用第一个函数。由于它是抽象定义的,我确信派生类必须有一个实现(即使它可能只是抛出 NotImplementException,但实现是有保证的)

我首先通过反射在泛型类本身中定义了第一个函数的实现。虽然这可行,但它非常慢,通过在控制器操作的开始/结束时启动/停止秒表来进行性能基准测试,仅 100 行大约需要 35 秒,所以它肯定不适合生产使用。

注意事项

  • 静态不能定义为抽象
  • 无法从静态上下文访问实例成员
  • 由于性能问题而无法使用反射

我猜可能的解决方案是最接近的(但我无法理解如何在我的情况下使用它们)

  • 将所有方法转换为实例方法并使用单例
  • 使用接口
  • 在派生类中定义一个静态方法并假设它在所有类中都存在,如果我这样做,那么在这种情况下如何访问 T 上的静态方法

我想要实现的是 ListAll 函数应该调用第一个接受动态对象的函数。

一些非常接近的问题是这些,但没有一个能解决我的问题。

Stack Overflow Q1Stack Overflow Q2Stack Overflow Q3

【问题讨论】:

  • 我不清楚你在问什么。如果这是您所追求的,您不能从同一类型的静态方法调用实例方法。另外,我不会因为这段代码每秒只能处理 3 条记录而责怪反射,那段时间很可能花在查询数据库上。
  • 看起来你的Class1&lt;T&gt; 和派生是某种简单的映射器,对吗?
  • @IvanStoev 是的。每个派生类将代表数据库中的一个表,而基本泛型将包含像 CRUD 操作这样的公共代码。我已经输入了大部分代码,只删除了与问题无关的部分。
  • 您需要将基本类型放入通用Where 过滤器中。这让你陷入了鸡和蛋的场景。您可能需要创建一个界面。
  • @RubberDuck 我同意鸡和蛋的情况,这正是我面临的问题,我可以在通用 where 过滤器中使用基本类型,但在我的情况下不是。我也觉得接口是一个可能的答案,我已经提到过,但我仍然不确定它们如何适合我的场景,所以需要指导。

标签: c# generics inheritance


【解决方案1】:

看起来设计应该是这样的

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;
    }
}

和示例实现

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);
        // ...
    }
}

基本上,您会探索 .NET 泛型类约束 (T : Class1&lt;T&gt;) 支持的 Curiously recurring template pattern,以确保派生类包含抽象的 Load 方法,而 new T() 部分由 new() 约束强制执行。

【讨论】:

  • 我试过这个但我得到了同样的错误'T'不包含'Load'的定义并且没有扩展方法'Load'接受'T'类型的第一个参数(您是否缺少 using 指令或程序集引用?)Core C:\Users\Abhishek\OneDrive\Visual Studio Online Workspace\Arinsys Library Collection\Core\Database\DatabaseORM.cs
  • 你是否改变了答案中的约束?
  • 我很抱歉,我错过了。现在可以了。万分感谢。虽然我更愿意在被覆盖的函数中检查 null,因为也有可能从程序中的其他点调用该函数。
  • @IvanStoev 可能跑题了,但我觉得奇怪的是你觉得这个答案值得回答,但不值得投票......
  • @RubberDuck 我不得不承认我专注于回答(解决、帮助)部分问题,而不是问题的有用性,因此没有投票问题的习惯(尽管我确实投票了很多答案)。但我接受批评。
猜你喜欢
  • 2021-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多