使用泛型转换方法在抽象类的不同实现之间进行转换

时间:2017-03-16 12:47:58

标签: c# generics type-conversion

我有一个抽象类,其中包含许多固定对象,通常是各种类型的字典和列表。然后是ReadData和WriteData的抽象方法。然后,我有两个不同的抽象类实现,一个根据“文本记录”标准写入数据,另一个根据定义的XML模式编写数据。

除了不同的阅读和写作方式之外,这两种实现方式是相同的。

我现在要做的是读取格式1中的数据,然后将其写为格式2.我可以通过编写.ToFormat2().ToFormat1()等方法轻松完成此操作。各个类,如果我想要完整性,可能.FromFormat2().FromFormat1()。但是这些例程本质上是相同的,当我需要格式3时,我正在思考(不是太遥远)的未来,并且不想在每个方法中快乐地实现两个或更多相同的“To”方法。类。这是浪费时间,难以调试/改变,只是不好。

所以我一直在尝试在抽象类中编写泛型转换器。以下代码说明了到目前为止我所做的工作原理:

public abstract class Test
{
    public string Type;
    public Dictionary<string, string> Dic1;
    public Dictionary<string, int> Dic2;

    public abstract void Read(string fileName);
    public abstract void Write(string fileName);

    public T ConvertTo<T>() where T : Test
    {
        T x = new T();
        if (x.Type.Equals(this.Type)) { return this; }
        x.Dic1 = this.Dic1;
        x.Dic2 = this.Dic2;
        return x;
    }
}

public class Format1 : Test
{
    // Constructor
    public Format1() { Type = "Format1"; Dic1 = new Dictionary<string, string>(); Dic2 = new Dictionary<string, int>(); }

    // Concrete implementations of abstract Read and Write for "Format1"
    public override void Read(string fileName) { /* do reading stuff */ }
    public override void Write(string fileName) { /* do writing stuff */ }
}

public class Format2 : Test
{
    // Constructor
    public Format2() { Type = "Format2"; Dic1 = new Dictionary<string, string>(); Dic2 = new Dictionary<string, int>(); }

    // Concrete implementations of abstract Read and Write for "Format2"
    public override void Read(string fileName) { /* do reading stuff */ }
    public override void Write(string fileName) { /* do writing stuff */ }
}

但编译器并不喜欢这样。当我将x声明为新T时,我收到错误,因为它没有new()约束,我无法返回this因为我无法隐式转换Test.TestT

我做错了什么?

5 个答案:

答案 0 :(得分:2)

您必须像这样更改ConvertTo<T>方法

public T ConvertTo<T>() where T : Test, new()

然后你会得到另一个错误,因为你必须像这样投出返回值

if (x.Type.Equals(this.Type)) { return (T)this; }

答案 1 :(得分:2)

由于抽象类(和接口)不能包含有关类型构造函数的契约,因此仅指定类型T的类型为Test是不足以保证编译器将存在(默认/无参数)构造函数。

因此,为了保证这一点,您必须扩展泛型类型参数约束以包含该条件:

public T ConvertTo<T>() where T : Test, new()

注意new()本质上表示“具有默认构造函数的类型”。

执行此操作后,您将遇到另一个问题,告诉您this无法转换为T。您必须在那里执行显式类型转换:

if (x.Type.Equals(this.Type)) { return (T)this; }

答案 2 :(得分:0)

您可以要求所有Test类都具有无参数构造函数,以便可以创建新实例:

public T ConvertTo<T>() where T : Test, new()
{
    T x = new T();
    if (x.Type.Equals(this.Type)) { return (T) this; }
    x.Dic1 = this.Dic1;
    x.Dic2 = this.Dic2;
    return x;
}

编辑:如果您不想/无法使用无参数构造函数,您可以将读/写方法与数据(字典和列表)分开,如下所示:

public interface IReadWriter
{
    void Read(Test test, string filenName);
    void Write(Test test, string filenName);
}

public class Test
{
    public string Type;
    public Dictionary<string, string> Dic1;
    public Dictionary<string, int> Dic2;
    public IReadWriter readWriter;

    public Test(IReadWriter readWriter)
    {
        this.readWriter = readWriter;
    }

    public void Read(string fileName)
    {
        readWriter.Read(this, fileName);
    }

    public void Write(string fileName)
    {
        readWriter.Write(this, fileName);
    }

    public Test WithReadWriter(IReadWriter other)
    {
        Test x = new Test(other);
        //if (x.Type.Equals(this.Type)) { return this; }
        x.Dic1 = this.Dic1;
        x.Dic2 = this.Dic2;
        return x;
    }
}

答案 3 :(得分:0)

link可能对您有所帮助: 当泛型类创建类型的新实例时,将新约束应用于类型参数。当您将new()约束与其他约束一起使用时,必须最后指定

    public T ConvertTo<T>() where T : Test,new()
    {
        T x = new T();
        if (x.Type.Equals(this.Type)) { return (this as T) ; }
        x.Dic1 = this.Dic1;
        x.Dic2 = this.Dic2;
        return x;
    }

答案 4 :(得分:0)

我已将@poke标记为答案,因为他回答了我询问有关通用转换器的问题。但是,我还接受了@Milney的评论,指出我继承了一些不应该继承的东西。所以这可能是做同样事情的更好方式,我为了完整起见而张贴。

它包含一个名为DataModel的类中的所有有趣位,然后将其放入接口中。每个类的构造函数允许我在其中一个重载中传递DataModel,这样我就可以根据前一个数据中的所有数据轻松创建一个新格式。

class Program
{
    static void Main(string[] args)
    {
        Format1 f1 = new Format1("5.10");
        f1.Data.Dic1.Add("Greet", "Hello World");
        f1.Data.Dic2.Add("RepeatGreet", 10);
        f1.Write("f1");
        Console.WriteLine("-------------------------------------------------------");

        Format2 f2 = new Format2("2.1","general",f1.Data);
        f2.Data.Dic1.Add("Goodbye", "See you later, Alligator");
        f2.Data.Dic2.Add("RepeatBye", 1);
        f1.Write("f1");
        f2.Write("f2");

        Console.ReadKey();
    }
}

public interface IDataFormat
{
    void Read(string filename);
    void Write(string filename);
    string Type { get; }
    string Version { get; }
    DataModel Data { get; }
}

public class DataModel
{
    public Dictionary<string, string> Dic1;
    public Dictionary<string, int> Dic2;

    // Constructor
    public DataModel() { Dic1 = new Dictionary<string, string>(); Dic2 = new Dictionary<string, int>(); }
}

public class Format1 : IDataFormat
{
    public string Type { get; }
    public string Version { get; }
    public DataModel Data {get; }

    // Constructors
    public Format1(string version) : this(version, new DataModel()) { }
    public Format1(string version, DataModel data) { Type = "Format1"; Version = version; Data = data; }

    // Concrete implementations of abstract Read and Write for "Format1"
    public void Read(string fileName) { /* do reading stuff */ }
    public void Write(string fileName)
    {
        Console.WriteLine("WRITING " + fileName +" IN FORMAT1:");
        Console.WriteLine("Type: " + Type + "\tVersion: " + Version);
        foreach (KeyValuePair<string, string> kvp in Data.Dic1) { Console.WriteLine("\t" + kvp.Key + "\t" + kvp.Value); }
        foreach (KeyValuePair<string, int> kvp in Data.Dic2) { Console.WriteLine("\t" + kvp.Key + "\t" + kvp.Value); }
    }
}

public class Format2 : IDataFormat
{
    // Properties
    public string Type { get; }
    public string SubType { get; set; }     // A property unique to this class
    public string Version { get; }
    public DataModel Data { get; }

    // Constructors. 
    // Including a constructor which is unique to this class because it uses a unique property of this class
    public Format2(string version) : this(version, "", new DataModel()) { }
    public Format2(string version, DataModel data) : this( version, "", data) { }
    public Format2(string version, string subType, DataModel data) { Type = "Format2"; Version = version; SubType = subType; Data = data; }

    // Concrete implementations of abstract Read and Write for "Format2"
    public void Read(string fileName) { /* do reading stuff */ }
    public void Write(string fileName)
    {
        Console.WriteLine("WRITING " + fileName + " IN FORMAT2:");
        Console.WriteLine("Type: " + Type + "........Version: " + Version);
        foreach (KeyValuePair<string, string> kvp in Data.Dic1) { Console.WriteLine("........" + kvp.Key + "........" + kvp.Value); }
        foreach (KeyValuePair<string, int> kvp in Data.Dic2) { Console.WriteLine("........" + kvp.Key + "........" + kvp.Value); }
    }
}