【问题标题】:Ways around creating an instance of a generic interface in C#在 C# 中创建泛型接口实例的方法
【发布时间】:2013-02-17 14:55:31
【问题描述】:

我已经把自己编码到一个角落里,希望你能帮助我再次把我挖出来。方向正确。

所以,我已经实现了一个小的 SQLite 包装器,我希望解决方案是通用的(不是我们所有人)。之后,我现在意识到这些类和接口的使用不是很直观,也不是通用的。

让我们从底部开始向上工作。我创建了一个名为DataRow 的类,它作为我的表行的基类。类DataRow 本身只有一个属性Id(因为所有行都需要一个)。这导致以下定义:class DataRow { public int Id { get; set; } }

使用这个DataRow 类,是每个表。对于数据库表,我创建了一个通用接口和一个通用基类。定义如下所示:

internal interface ITable<T>
    where T : DataRow, new()
{
    T Select(int id);
    List<T> Select(List<int> ids);
    int Insert(T t);
    void Update(T t);
    bool Delete(int id);
}

public class Table<T> : ITable<T>
    where T : DataRow, new()
{
    // Commented out to protect you from a minor case of serious brain damage.
}

此设置允许我创建简洁的定义。事实上,它们往往是相当史诗般的,真的。自豪地说。

public class Car : DataRow
{
    public decimal ParkingTicketDebt { get; set; }
    public DateTime WhenWifeWillAllowReplacement { get; set; }
    public bool CanTransformIntoSomethingAwesome { get; set; }
}

public class Cars : Table<Car> {
    // Yep, that's all. You can go home now, folks. There's nothing here. Nothing at all. Especially not a great treasure of gold. Whops... I mean... there's really not. Not that I'm aware of, anyway... I mean, there could be. Not that I wouldn't say if I had any information on this great trasure of gold that might exist. But I know nothing of such an item. I really don't, so you can stop thinking about this great treasure of gold. Since I don't know anything about it, the chance that it even exist is extremely low. Miniscule. I mean, you would probably not find anything, not even if you digged for, like, a really long time. Seven years or something. Oookay. Slowly fading away...
}

您可能注意到也可能没有注意到,我使用Cars 的类类型名称来确定数据库中表的名称。同样,我正在对Car 执行反射,并使用其公共属性名称和类型来获取/设置数据库中的值。是的,我知道我正在编写实体框架的精简版本。这听起来既愚蠢又费时。

无论如何,这里有一个 Cars 类的用法示例,我必须提醒您,我为之感到自豪:

new Cars().Delete(3497); // Note that I have a great number of (expensive) cars.

很好,嗯?一个小问题。这意味着我必须编写强类型代码,具体到数据库中存在的表的数量。而且我不喜欢特定的代码。我喜欢通用代码。

你可能会开始争辩说我做得过火了。那么让我告诉你这个。你说得对,我是矫枉过正!我故意用火焰喷射被坦克碾过的死人。七次。

所以我开始尝试了一下,想出了这个巧妙的解决方案:

[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
[WebMethod(EnableSession = true)]
public int CreateEmptyRow(string tableName)
{
    var tableType = Type.GetType(tableName);
    if (tableType == null)
        throw new TypeLoadException("Dumbass. That table doesn't exist");

    var instance = Activator.CreateInstance(tableType) as ITable<dynamic>;
    if (instance == null)
        throw new TypeLoadException("Idiot. That type isn't a table");

    return instance.Insert(new DataRow());
}

请注意,如果您不知道为什么有人要创建一个空行,我真的可以理解。

那么这有什么问题呢?嗯,它不编译,一方面。这是错误:There is no implicit reference conversion from 'dynamic' to 'DataRow'。在 Google 上搜索得到few results

问题显然是Activator.CreateInstance(tableType) as ITable&lt;dynamic&gt;。我尝试过Activator.CreateInstance(tableType) as ITable&lt;Table&lt;DataRow&gt;&gt; 之类的方法,但这种尝试给了我这个错误:The type 'DataRow' must be convertible to 'DataRow'

【问题讨论】:

  • 关于改进我的解决方案的建议将获得...奖励
  • 既然你已经知道你的 Cars 类实现了ITable&lt;t&gt;,你需要把它转换成接口吗?你能简单地离开as ITable&lt;dynamic&gt;吗?我发现这篇文章在过去很有帮助:How to Examine and Instantiate Generic Types with Reflection
  • 更改最少的解决方案,虽然可能不是最快/最有效的,但也是反映您想要调用的方法(而不仅仅是您实例化的类型)
  • @YavgenyP 我觉得一个重要的“DOH”时刻正在建立......
  • 是的,虽然我不确定dynamic 是否也很好(再次,性能方面)。最快的使用可能是Table 将实现的另一个接口(可以说ITable 但没有通用定义)。它将具有与其通用兄弟相同的方法,并且可以在您的 CreateEmptyRow 方法中对其进行强制转换...

标签: c# .net generics reflection interface


【解决方案1】:

所以,正如我在评论中所写,我添加了一个额外的非通用接口:

 interface ITable
{
    DataRow Select(int id);
    IEnumerable<DataRow> Select(List<int> ids);
    int Insert(DataRow t);
    void Update(DataRow t);

}
interface ITable<T>  where T : DataRow, new()
{
    T Select(int id);
    List<T> Select(List<int> ids);
    int Insert(T t);
    void Update(T t);
    bool Delete(int id);
}

class Table<T> : ITable<T>, ITable where T : DataRow, new()
{

    public T Select(int id)
    {
        return new T();
    }

    public List<T> Select(List<int> ids)
    {
        return new List<T>();
    }

    public int Insert(T t)
    {
        return 1;
    }

    public void Update(T t)
    {
    }

    public bool Delete(int id)
    {
        return true;
    }

    DataRow ITable.Select(int id)
    {
        return this.Select(id);
    }

    IEnumerable<DataRow> ITable.Select(List<int> ids)
    {
        return this.Select(ids);
    }

    public int Insert(DataRow t)
    {
        return this.Insert(t);
    }

    public void Update(DataRow t)
    {
        this.Update(t);
    }
}

这就是我实现CreateEmptyRow \ Select 方法的方式:

      public static int CreateEmptyRow(string tableName)
    {
        var tableType = Type.GetType(tableName);
        if (tableType == null)
            throw new TypeLoadException("Dumbass. That table doesn't exist");

        var instance = Activator.CreateInstance(tableType) as ITable;
        if (instance == null)
            throw new TypeLoadException("Idiot. That type isn't a table");

        return instance.Insert(new DataRow());
    }
    public static List<DataRow> Select(List<int> ids, string tableName)
    {
        var tableType = Type.GetType(tableName);
        if (tableType == null)
            throw new TypeLoadException("Dumbass. That table doesn't exist");

        var instance = Activator.CreateInstance(tableType) as ITable;
        if (instance == null)
            throw new TypeLoadException("Idiot. That type isn't a table");

        return instance.Select(ids).ToList();
    }

注意,如果你想要这样一个通用的解决方案,select方法(例如)只能返回DataRowIEnumerable\List,这可以通过使用提供的Cast扩展方法来解决:

var myList = Select(null, "Cars").Cast<Car>();

注意:您可能知道,要通过名称实例化 Cars 类,您还需要提供命名空间,我在此略过,而且 Table&lt;T&gt; 类可能也应该是抽象的。

【讨论】:

  • 这不能直接工作,我必须创建一个额外的接口IDataRow,因为DataRow 不能转换为T。但是我的最终解决方案最终与您的解决方案非常接近。所以非常感谢!我将发布我的最终解决方案以供他人取乐
  • 酷,很高兴我能帮上忙 :)
【解决方案2】:

一个问题是您尝试将DataRow 插入到一个表中,该表采用DataRow 的某个子类,因此即使您可以编译它,您仍然会在运行时遇到异常。

您需要找到通用行类型来插入并插入该类型的新实例:

object instance = Activator.CreateInstance(tableType);
var tableInterface = tableType.GetInterfaces().FirstOrDefault(it => it.IsGenericType && it.GetGenericTypeDefinition() == typeof(ITable<>));
if(tableInterface == null) throw new ArgumentException("Type is not a table type");

var rowType = tableInterface.GetGenericArguments()[0];
var newRow = Activator.CreateInstance(rowType);

MethodInfo insertMethod = tableInterface.GetMethod("Insert");
return (int)insertMethod.Invoke(instance, new object[] { newRow });

不过,您似乎可以将 CreateEmptyRow 方法在表和行类型中通用,并完全避免反射:

public int CreateEmptyRow<TTable, TRow>() 
    where TRow : DataRow, new()
    where TTable : ITable<TRow>, new()
{
    var table = new TTable();
    return table.Insert(new TRow());
}

【讨论】:

  • 很抱歉给您带来了困惑。它是一种 Web 服务方法。我会编辑我的问题!
  • 这给了我一个RuntimeBinderExceptionThe best overloaded method match for 'Table&lt;Car&gt;.Insert(Car)' has some invalid arguments。在检查newRow 时,我可以看到它是Car 的一个完全真实的实例。有什么想法吗?
  • @Simeon - 我已更改代码以通过反射而不是使用动态调用 Insert 方法 - 这是否解决了问题?
  • 是的,从技术上讲,这是一个解决方案。但是,它缺少大局。这种技术(最初由 YavgenyP 建议)导致几乎无法使用的 SQLite 包装器。我想创建一个漂亮的界面供其他开发人员使用! :)
  • 再说一次,我不能将CreateEmptyRow 设为通用,因为它是一种 ASMX 方法。除非,我的魔法朋友,你也可以解决这个问题:stackoverflow.com/questions/9194603/…
猜你喜欢
  • 1970-01-01
  • 2011-05-19
  • 1970-01-01
  • 2012-11-24
  • 1970-01-01
  • 2022-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多