答:抽象B:抽象C <b> - 允许C调用A?</b>的实例

时间:2012-02-08 23:30:50

标签: c# generics

其他信息

我很欣赏重新定义问题的尝试,但有问题的结构是不可协商的。我正在使用2004年在.NET 1.1中创建的代码库,然后再使用泛型和许多其他不错的功能。我无法打破现有的模型 - 只能增强它(因此将C转换为C - 总体上实际上是C:C,其中C只是为了保留现有的继承依赖性而保留。

我们8岁的数据层是一个穷人的ORM - 它在一个类中封装了实体功能(即:表字段)和数据功能(连接和提取数据)。抽象泛型C和抽象B是由我们自2004年以来使用的实用程序应用程序生成的。这就是为什么B是抽象的 - 所以开发人员不得不考虑将B的真实实例化为类型A.(这不是完全成功的,诚然)

此模型中有超过100个表,因此超过100&#34; A&#34;我们项目中的课程。我不能采用任何需要手动更新每个A类的更改。我所做的任何更改都必须以生成的方式完成 - 理想情况下,尽可能低于继承根。

现有的select方法返回数据集,而不是自身。如果我要改进它们只是让它们使用数据读取器并返回它们自己的实例和列表。

非常感谢那些到目前为止已经回复过的人,但实际上帮助我的问题是对所提出问题的回答,而不是尝试重新定义问题以适应将会出现的问题。理想的新应用程序。


原创内容:

我有一个基本的抽象泛型类C

public abstract class C<T>

从中派生出一个抽象类B

public abstract class B : C<B>

最后A实例化B

public class A : B

B有返回B列表的方法,需要能够调用C的实例来填充列表。我希望该方法将第二代后代实例化为C。

public abstract class C<T>
{
    protected abstract PopulateList(List<T> theList, SqlDataReader dr);
    protected TI GetNew<TI>() where TI:T, new() {return new TI();}
}

非常好,但是现在在B中我需要PopulateList方法能够实例化新的A对象来从提供B共有数据的datareader填充它们

public abstract class B : C<B>
{
    public int MyInt {get;set;}

    protected override void PopulateList(List<B> DistributionList, SqlDataReader dr)
    {
        while (dr.Read())
        {
            Distribution newOne = GetNew<B>();

            newOne.MyInt = (Int32)dr["MyInt"];
            DistributionList.Add(newOne);
        }
    }
}

那么我还需要制作B通用吗? B<T> : C<T> where T:B并以与GetOne()相同的方式重现填充列表?或者有什么方法可以强制PopulateList调用从B派生的当前类型的新实例而不添加新的泛型层?

并不是说新的generics层本身就不是优选的

2 个答案:

答案 0 :(得分:1)

我认为通过以下方式做得更好:

//Could be an abstract, but an interface is better here
public interface ICanRead
{
    void ReadOne(IDataReader dr);
}

public class B : ICanRead
{
    public int MyInt {get;set;}

    //The interface implementation can be private to 'hide' it from the public
    void ICanRead.ReadOne(IDataReader dr)
    {
        Read(dr);
    }

    protected virtual void Read(IDataReader dr)
    {
        MyInt = (Int32)dr["MyInt"];
    }
}

public class A : B
{
    public int MyOtherInt {get;set;}

    protected override void Read(IDataReader dr)
    {
        base.Read(dr);
        MyOtherInt = (Int32)dr["MyOtherInt"];
    }
}

现在,您可以使用其他一些类来包含用于读取列表的代码。基本上你是在混合这里的概念,使对象做容器应该做的事情。当然,为了方便起见,你总是可以将ReadList / PopulateList类型的东西添加到ICanRead的扩展方法中,但创建一个类型为“A”的类是愚蠢的,这样你就可以创建一个新的列表。

另外一个问题是,我认为允许A或B的未初始化版本存在可能会造成问题。我建议该对象采用IDataReader,IDictionary或其他可以完全初始化的接口。这使您有机会在反序列化后验证数据的完整性。我倾向于远离IDataReader,因为它是一个相当冗长的界面,而只是定义一个简单的名称/值查找界面并使用它。然后,您可以创建一个适配器类,以提供适当的实现。

public class Database
{
    public static IEnumerable<T> ReadList<T>(IDataReader dr, Func<T> Create)
        where T : ICanRead
    {
        while (dr.Read())
        {
            T newOne = Create();
            newOne.ReadOne(dr);
            yield return newOne;
        }
    }

    public static void PopulateList<T>(List<T> list, IDataReader dr)
           where T : ICanRead, new()
    {
        list.AddRange(ReadList(dr, () => new T()));
    }
}

注意:我不建议使用上面的PopulateList方法。使用通用的“new()”应该非常小心,因为与提供代表相比,它可能非常昂贵。请参阅以下帖子:Optimization and generics, part 1: the new() constraint

注意2:很明显,您的意图是创建一个ORM图层。除非只是为了你的教育,否则我会强烈建议不要这样做。有很多好的ORM技术,我会选择MEF或NHibernate,但不要构建它。

注3:虽然IEnumerable / yield很酷,但要小心。确保枚举完成后再重新使用阅读器连接或关闭阅读器的连接。

答案 1 :(得分:1)

如果您愿意在GetNew()中对class C<T>进行此更改,则可以在没有更多泛型的情况下实现此目的:

protected abstract T GetNew();

B.PopulateList

B newOne = GetNew();

class A

protected override B GetNew()
{
    return new A();
}

更新:如果对class A进行任何更改是不切实际的,那么您可以在GetNew()中定义C<T>,如下所示:

protected T GetNew()
{
    return (T)Activator.CreateInstance(this.GetType());
}

不雅,是的,但它完成了工作。