QA@IT

戻り値の型を、引数の型によって動的に決定したい。

11233 PV

C# .NET 2010 で開発しています。
前回 http://qa.atmarkit.co.jp/q/3132
と似たような質問になってしまい申し訳ありません。

目的:戻り値の型を、引数の型によって動的に決定したい。

基底クラスが2つあり、それぞれの派生クラスが1対1対応しているとします。

  • 基底クラス:BaseCs、BaseXml
  • BaseCsの派生クラス:UserCs、ProductCs
  • BaseXmlの派生クラス:UserXml、ProductXml

BaseXmlクラスには、ToCs()メソッドがあり、
対応するBaseCsの派生クラスに変換できるとします。

任意のBaseCs派生クラスを引き渡すと、
対応するBaseXml派生クラスを返す、
ジェネリックメソッドを作りたいのですが、可能でしょうか?

※BaseCs、BaseXmlは実体を持たないため、可能であれば抽象クラスが望ましいです。

※サンプルコード中のTo[Type]Nullable()メソッドは、
文字列を指定Typeの型に変換し、変換できない場合はNullを返す、string型の拡張メソッドです。

基底クラス

public abstract class BaseCs{}
public abstract class BaseXml{
        //public abstract BaseXml();
        //public abstract BaseXml(BaseCs c);
        //public abstract BaseCs ToCs();
    }

BaseCs派生クラス

public class UserCs : BaseCs
{
    public int Id = 0;
    public string Name = string.Empty;
    public DateTime Birthday = DateTime.MinValue;
}

public class ProductCs : BaseCs
{
    public int Id = 0;
    public string Name = string.Empty;
    public decimal Price = 0m;
}

BaseXml派生クラス

public class UserXml : BaseXml
{
    public string Id = string.Empty;
    public string Name = string.Empty;
    public string Birthday = string.Empty;

    public UserXml(){}
    public UserXml(UserCs c)
    {
        this.Id = c.Id.ToString();
        this.Name = c.Name;
        this.Birthday = c.Birthday.ToString();
    }
    public UserCs ToCs()
    {
        UserCs c = new UserCs();
        c.Id = this.Id.ToIntNullable() ?? 0;
        c.Name = this.Name;
        c.Birthday = this.Birthday.ToDateTimeNullable() ?? DateTime.MinValue;
        return c;
    }
}

public class ProductXml : BaseXml
{
    public string Id = string.Empty;
    public string Name = string.Empty;
    public string Price = string.Empty;

    public ProductXml(){}
    public ProductXml(ProductCs c)
    {
        this.Id = c.Id.ToString();
        this.Name = c.Name;
        this.Price = c.Price.ToString();
    }
    public ProductCs ToCs()
    {
        ProductCs c = new ProductCs();
        c.Id = this.Id.ToIntNullable() ?? 0;
        c.Name = this.Name;
        c.Price = this.Price.ToDecimalNullable() ?? 0m;
        return c;
    }
}

最初に考えたのは、このパターンなのですが、
これだとBaseCs、BaseXmlの基底クラスを抽象クラスから実体クラスにする必要があり、
かつ、戻り値はあくまで基底クラスで派生クラスでは返ってくれないので、断念しました。

public BaseXml ToXml(BaseCs c)
    {
        BaseXml x;

        if (c is UserCs) x = new UserXml((UserCs)c);
        else if (c is ProductCs) x = new ProductXml((ProductCs)c);
        else x = new BaseXml();

        return x;
    }

作りたいジェネリックメソッドのイメージ

public <Type2> ToXml<Type>(Type c)
    where Type : BaseCs
{
    Type2 x; // BaseXml派生クラス
    x = new <Type2>(c);
    return x;
}

よろしくお願い致します。

回答

想定通りかどうかはわかりませんが、
以下の様に継承時に対応させるクラスを指定してあげるのは可能です。

public abstract class BaseCs { }
public abstract class BaseXml<T>
{
    public abstract T ToCs();
}
public class UserCs : BaseCs
{
    public DateTime Birthday = DateTime.MinValue;
}

public class ProductCs : BaseCs
{
    public decimal Price = 0m;
}

public class UserXml : BaseXml<UserCs>
{
    public string Birthday = string.Empty;

    public UserXml() { }
    public UserXml(UserCs c)
    {
        this.Birthday = c.Birthday.ToString();
    }

    public override UserCs ToCs()
    {
        UserCs c = new UserCs();
        return c;
    }
}

public class ProductXml : BaseXml<ProductCs>
{
    public string Price = string.Empty;

    public ProductXml() { }
    public ProductXml(ProductCs c)
    {
        this.Price = c.Price.ToString();
    }

    public override ProductCs ToCs()
    {
        ProductCs c = new ProductCs();
        return c;
    }
}

ちなみにこれもまた静的に決定しており、「メソッド定義の戻り値の型の部分を、動的に決定」することはできません。
最初に思いつかれたパターンのように基底クラスを戻りの型にしてあげるぐらいしかないでしょう。最初に思いつかれたパターンでも、UserCsでもProductCsでもない場合は例外を投げてしまえば、基底クラスは抽象クラスのままにできると思います。

編集 履歴 (2)
  • 回答ありがとうございます。
    継承時に対応クラスを指定する方法があるのですね。
    この場合、UserXmlやProductXmlは、BaseXml<Type>に変換できるのでしょうか?
    -

コードは BaseDataがないのでエラーになります。

クラスが増えてきていて、ちょっとWeb上ではアドバイスは難しいかと思います。 設計思想とかもっと単純には何がOKでどういうのはイヤなのかがわからないとアドバイスできないので(かつ、ここではちょっとそこまでは難しそう)

さて、前の回答でも

気を付けて欲しいですが、ToCsメソッドにしてもConvertToXmlメソッドにしても、戻り値の型がちがうのであればたまたま名前がおなじだった別のメソッドで、共通化されていません(生成するものが違うのですから別個の機能です)。
そのあたりも踏まえて何を共通化させたいか、どんな汎用的な機能を親クラスに持たせるべきなのか、何は共通化できないのかなどを整理された方がいいように思います。クラスにどういう事をさせたいかを考えずにとりあえずでまとめようとしてしまっていませんか?

というような事を言いましたが、もう少し具体的に。

実装の流れをみるに機能を限定した後に制限前の汎用性を使おうとしている様に見えます。
具体的には

public class UserCs : BaseCs<UserXmlData>

public class UserXmlData : BaseXml<UserCs>

は、T に具体的なクラスを決定してしまっていますので汎用性はありません。

型が限定されていると広がりがない(だがObjectでは安全でない)といった点では

public string ToSomething(){
    return "";
}

public T ToSomething<T>()
    where T : new(){
    return new T();
}

とすれば Tを指定できる分汎用的になりますが、継承時にBaseXmlやBaseCs型を指定しているのでこれと逆の事が起きています。
なので、これらをBaseCsとBaseXmlを使って汎用的に振る舞わせることはもはやできません。すでに制限したからです。

UserCsとProductCsは共通の親を持ちません。ではそもそも継承しなくてはいけないのかを考えてみてください。
Tに依存しない共通で利用できるメソッドがあればメリットはありますが、たとえば以下ではだめなんでしょうか。
(継承してますが、継承しないパターンもあるし以下の様にTの解決を遅らせてBaseをTから自由にすることもできます)

public interface IConvertableXml<T>{
    T ToXml();
}

public interface IShowableParameter{
    string ShowParameter();
}

public abstract class BaseCs
    : IShowableParameter
{
    public int Id = 0;
    public string Name = string.Empty;
    public abstract string ShowParameter();
}

public class UserCs : BaseCs, IConvertableXml<UserXmlData>
{ /*実装省略*/
}

public class ProductCs : BaseCs, IConvertableXml<ProductXmlData>
{ /*実装省略*/
}

うまく説明できないですが、自分が実装する拡張・制限などがどう影響を及ぼすのか、誰が持つのが自然なのかを見直してみたほうがいいかもしれません。機能を使うことに意識が行き過ぎているような気がします。

編集 履歴 (1)

今までご教授頂いた事から、ToCs()とToXml()をそれぞれに実装してみました。

この状態で、BaseCs派生クラスまたはBaseXml派生クラスの
それぞれをリストで保持するクラスを作りたいのですが、
ToCs()とToXml()のために、互いに<T>をつけて宣言しているため、
作ろうとするとループしてしまって上手くいきません。

同じような所で引っ掛かってばかりで申し訳ありませんが、
今一度お教え頂けますでしょうか。

基底クラス

public interface IConvertableXml<T>{
    T ToXml();
}
public interface IShowableParameter{
    string ShowParameter();
}
public abstract class BaseCs<T>
    : IConvertableXml<T>, IShowableParameter{
    public int Id = 0;
    public string Name = string.Empty;
    public abstract T ToXml();
    public abstract string ShowParameter();
}

public interface IConvertableCs<T>{
    T ToCs();
}
public interface IConvertableXmlString{
    string ToXmlString();
}
public abstract class BasicData<T>
    : Njs.Common.Data.BaseData, IConvertableCs<T>{
    public abstract T ToCs();
}
public abstract class BaseXml<T>
    : BasicData<T>, IConvertableXmlString{
    public abstract string ToXmlString();
}

BaseCsの派生クラス

public class UserCs : BaseCs<UserXmlData>{
    public DateTime Birthday = DateTime.MinValue;

    public override string ShowParameter()
    {
        //throw new NotImplementedException();
        return this.Birthday.ToString();
    }

    public override UserXmlData ToXml()
    {
        //throw new NotImplementedException();
        return new UserXmlData(this);
    }
}

public class ProductCs : BaseCs<ProductXmlData>{
    public decimal Price = 0m;

    public override string ShowParameter()
    {
        //throw new NotImplementedException();
        return this.Price.ToString();
    }

    public override ProductXmlData ToXml()
    {
        //throw new NotImplementedException();
        return new ProductXmlData(this);
    }
}

BaseXmlの派生クラス

public class UserXmlData
    : BaseXml<UserCs>
{
    public string Birthday = string.Empty;

    public UserXmlData(){}
    public UserXmlData(UserCs c){
        this.Id = c.Id.ToString();
        this.Name = c.Name;
        this.Birthday = c.Birthday.ToString();
    }
    public override UserCs ToCs(){
        //throw new NotImplementedException();
        return new UserCs();
    }
    public override string ToXmlString(){
        //throw new NotImplementedException();
        return Encoding.UTF8.GetString(this.ToByteArray(typeof(UserXmlData)));
    }
}

public class ProductXmlData
    : BaseXml<ProductCs>
{
    public string Price = string.Empty;

    public ProductXmlData(){}
    public ProductXmlData(ProductCs c){
        this.Id = c.Id.ToString();
        this.Name = c.Name;
        this.Price = c.Price.ToString();
    }
    public override ProductCs ToCs(){
        //throw new NotImplementedException();
        return new ProductCs();
    }
    public override string ToXmlString(){
        //throw new NotImplementedException();
        return Encoding.UTF8.GetString(this.ToByteArray(typeof(ProductXmlData)));
    }
}

ここまでは問題なく動作することを確認しました。

以下、問題のリストクラスです。
BaseCs派生クラスのリストクラス

public class UpdateCs<T> // <T>には、BaseCs派生クラスを指定
    //where T : BaseCs<T> // BaseCsの継承を条件にしたいが、UpdateCs<T>とBaseCs<T>の<T>が異なるため不可能
    //where T : IConvertableXml<BaseXml<T>> // 同様に、Update<T>とIConvertableXml<T>の<T>が異なるため不可能
    where T : IShowableParameter{ 
    public int LoginId = 0;
    public List<T> Items = new List<T>(); // BaseCs派生クラスのリスト

    public string ShowParameter()
    {
        //throw new NotImplementedException();
        StringBuilder sb = new StringBuilder();
        foreach (T item in this.Items)
        {
            sb.Append(item.ShowParameter());
            sb.Append("\r\n");
        }
        return sb.ToString();
    }

    //public override UpdateXml<T> ToXml() // ここでの<T>は、BaseXml派生クラスの必要がある、はず。
    //{
    //    return new UpdateXml<T>();
    //}
}

ここまでで、意図するものと多少違いますが、一応動作します。

以下のクラスは、コンパイルエラーに阻まれて実装出来ていません。
上記クラスに対応するBaseXml派生クラスのリストクラス

public class UpdateXml<T> // <T>には、BaseXml派生クラスを指定
    where T : BaseXml<T> // BaseXmlの継承を条件にしたいが、UpdateXml<T>とBaseXml<T>の<T>が異なるため不可能
{
    public string LoginId = string.Empty;
    public List<T> Items = new List<T>();

    public override UpdateCs<T> ToCs() // ここでの<T>は、BaseCs派生クラスの必要がある、はず。
    {
        //throw new NotImplementedException();
        UpdateCs<T> c = new UpdateCs<T>();
        foreach(T item in this.Items)
        {
            c.Items.Add(item.ToCs());
        }
        return c;
    }

    public override string ToXmlString()
    {
        //throw new NotImplementedException();
        StringBuilder sb = new StringBuilder();
        sb.Append(Encoding.UTF8.GetString(this.ToByteArray(typeof(UpdateXml<T>))));
        return sb.ToString();
    }
}

メインのイメージです。

public class Main{
    public void Test()
    {
        UserCs uc1 = new UserCs();
        uc1.Id = 11;
        uc1.Name = "山田太郎";
        uc1.Birthday = ("19831006").ToDateTime();

        UserXmlData ux_to = uc1.ToXml();
        ux_to.Birthday = "2000/01/01";

        UserXmlData ux_new = new UserXmlData(uc1);
        ux_new.Birthday = "2005/12/31";

        UserCs uc_to = ConvertToCs<UserCs>(ux_to);
        UserCs uc_new = ux_new.ToCs();

        UpdateCs<UserCs> updateUserCs = new UpdateCs<UserCs>();
        updateUserCs.Items.Add(uc1);
        updateUserCs.Items.Add(uc_to);
        updateUserCs.Items.Add(uc_new);
        richTextBox1.Text += "【UpdateCs<UserCs>】\r\n";
        richTextBox1.Text += updateUserCs.ShowParameter();

        /* これらを実装したいです。
        UpdateXml<UserXml> updateUserXml = new UpdateXml<UserXml>();
        updateUserXml.Items.Add(ux_to);
        updateUserXml.Items.Add(ux_new);
        richTextBox1.Text += "【UpdateXml<UserXml>】\r\n";
        richTextBox1.Text += updateUserXml.ToXmlString();

        updateUserXml = updateUserCs.ToXml();
        richTextBox1.Text += "【UpdateXml<UserXml>】\r\n";
        richTextBox1.Text += updateUserXml.ToXmlString();
        */
    }
}
編集 履歴 (1)
BaseXml

BaseXml<T>

BaseXml<UserCs>

が違う型なのは理解しました。

では、例えば戻り値がstring型で同一だった場合、これらはジェネリックメソッドが使用可能なのでしょうか?
BaseXmlは型違いになるので、string型で帰るインターフェイスを継承して、
ジェネリックメソッドでの条件として、インターフェイスを指定する方法になる、でしょうか?

interface IConvertableXmlString
{
    string ToXmlString();
}
public abstract class BaseXml<T>
    : BasicData<Type>, IConvertableXmlString
{
    public abstract T ToCs();
    public abstract string ToXmlString();
}
public class UserXml : BaseXml<UserCs>{
    public string Birthday = string.Empty;

    public UserXml() { }
    public UserXml(UserCs c){
        this.Birthday = c.Birthday.ToString();
    }

    public override UserCs ToCs(){
        UserCs c = new UserCs();
        return c;
    }

    public override string ToXmlString(){
        string xml = string.Empty;
        xml += this.Birthday;
        return xml;
    }
}

public class ProductXml : BaseXml<ProductCs>{
    public string Price = string.Empty;

    public ProductXml() { }
    public ProductXml(ProductCs c){
        this.Price = c.Price.ToString();
    }

    public override ProductCs ToCs(){
        ProductCs c = new ProductCs();
        return c;
    }

    public override string ToXmlString(){
        string xml = string.Empty;
        xml += this.Price;
        return xml;
    }
}
public class Main{
    public void ShowXmlParameter<Type>(Type t)
        // where Type : BaseXml // 型違いのためジェネリックスは不可能
        where Type : IConvertableXmlString
    {
        Consol.Write(t.ToXmlString());
    }

    public void Main(){
        UserCs uc = new UserCs();
        ProductCs pc = new ProductCs();

        UserXml ux = new UserXml(uc);
        ProductXml px = new ProductXml(pc);

        ShowXmlParameter<UserXml>(ux);
        ShowXmlParameter<ProductXml>(px);
    }
}
編集 履歴 (3)

無理矢理感が否めませんが、1対1対応の片方向に限れば、
引数型によって戻り値型を操作できました。
しかし、逆変換が出来ません。

BaseCsの基底クラス

public abstract class BaseCs
{
    public int Id = 0;
    public string Name = string.Empty;
    public abstract string ShowParameter();
}

BaseCsの派生クラス

public class UserCs : BaseCs
{
    public DateTime Birthday = DateTime.MinValue;

    public override string ShowParameter()
    {
        //throw new NotImplementedException();
        StringBuilder sb = new StringBuilder();
        sb.Append(this.Id.ToString());
        sb.Append(", ");
        sb.Append(this.Name);
        sb.Append(", ");
        sb.Append(this.Birthday.ToString());
        return sb.ToString();
    }
}

public class ProductCs : BaseCs
{
    public decimal Price = 0m;

    public override string ShowParameter()
    {
        //throw new NotImplementedException();
        StringBuilder sb = new StringBuilder();
        sb.Append(this.Id.ToString());
        sb.Append(", ");
        sb.Append(this.Name);
        sb.Append(", ");
        sb.Append(this.Price.ToString());
        return sb.ToString();
    }
}

BaseXmlのインターフェイス、抽象クラス、基底クラス

interface IConvertableCs<Type>
{
    Type ToCs();
}

public abstract class BasicData<Type>
    : IConvertableCs<Type>
{
    public abstract Type ToCs();
}

public abstract class BaseXml<Type>
    : BasicData<Type>
    where Type : BaseCs
{
    public string Id = string.Empty;
    public string Name = string.Empty;
}

BaseXmlの派生クラス

public class UserXmlData
    : BaseXml<UserCs>
{
    public string Birthday = string.Empty;

    public UserXmlData(){}
    public UserXmlData(UserCs c)
    {
        this.Id = c.Id.ToString();
        this.Name = c.Name;
        this.Birthday = c.Birthday.ToString();
    }
    public override UserCs ToCs()
    {
        //throw new NotImplementedException();
        UserCs c = new UserCs();
        c.Id = this.Id.ToIntNullable() ?? 0;
        c.Name = this.Name;
        c.Birthday = this.Birthday.ToDateTimeNullable() ?? DateTime.MinValue;
        return c;
    }
}

public class ProductXmlData
    : BaseXml<ProductCs>
{
    public string Price = string.Empty;

    public ProductXmlData(){}
    public ProductXmlData(ProductCs c)
    {
        this.Id = c.Id.ToString();
        this.Name = c.Name;
        this.Price = c.Price.ToString();
    }
    public override ProductCs ToCs()
    {
        //throw new NotImplementedException();
        ProductCs c = new ProductCs();
        c.Id = this.Id.ToIntNullable() ?? 0;
        c.Name = this.Name;
        c.Price = this.Price.ToDecimalNullable() ?? 0m;
        return c;
    }
}

BaseXmlの派生クラスから、BaseCsの派生クラスは生成できました。

public Type TestConvert<Type>(BaseXml<Type> x)
    where Type : BaseCs
{
    return x.ToCs();
}

しかし、当初の目的である方向の変換が出来ません。

public BaseXml<Type> ConvertToXml<Type>(Type c)
    where Type : BaseCs
{
    // 型'UserXmlData'を型'BaseXml<Type>'に変換できません。
    if (c is UserCs) return (BaseXml<Type>)new UserXmlData(c as UserCs);
    // 型'ProductXmlData'を型'BaseXml<Type>'に変換できません。
    else if (c is ProductCs) return (BaseXml<Type>)new ProductXmlData(c as ProductCs);
    return null;
}

BaseXml型を継承しながら、変換できないのは何故なんでしょうか?

編集 履歴 (1)

XmlParameterとなっているところはXmlStringに直し忘れと思っていいですか?

Mainクラスの部分ですが、インターフェースでしぼるなら、Genericメソッドではなくてインターフェースを受け取るのが素直だと思います。

public class Main{
    public void ShowXmlParameter(IConvertableXmlString t)
    {
        Consol.Write(t.ToXmlString());
    }

    public void Main(){
        UserCs uc = new UserCs();
        ProductCs pc = new ProductCs();

        UserXml ux = new UserXml(uc);
        ProductXml px = new ProductXml(pc);

        ShowXmlParameter(ux);
        ShowXmlParameter(px);
    }
}

という回答であってます?(「使用可能か?」というのが気になりましたが)
あとインターフェースの場合は「継承する」ではなく「実装する」と言います。

編集 履歴 (0)
  • すみません、直し忘れです。
    なるほど、インターフェイスで受け渡すという方法もあるのですね。
    ついついGenericに意識を持って行かれてしまうのですが、実行したいのはあくまでインターフェイスが実装するメソッド、ですね。
    -
  • その通りです。多分どちらの書き方でも機能的に違いはないと思いますけどね。 -
基底クラスA
基底クラスB

派生クラスA1:基底クラスA
派生クラスA2:基底クラスA

派生クラスB1:基底クラスB
派生クラスB2:基底クラスB

new 派生クラスB1(派生クラスA1);
new 派生クラスB2(派生クラスA2);

派生クラスA1 = 派生クラスB1.ToData();
派生クラスA2 = 派生クラスB2.ToData();

上記条件下で、

  • 派生クラスA1を引数として渡すと派生クラスB1
  • 派生クラスA2を引数として渡すと派生クラスB2

このように返ってくるメソッドを作りたいのですが、これは不可能なのでしょうか?

この方法だと、返ってくるのが基底クラスBにアップキャストされた状態、なので、
ジェネリックスとは違いますよね。

public 基底クラスB ConvertAtoB(基底クラスA a)
{
    if (a is 派生クラスA1) return (基底クラスB)new 派生クラスB1(a as 派生クラスA1);
    else if (a is 派生クラスA2) return (基底クラスB)new 派生クラスB2(a as 派生クラスA2);
    return null;
}
編集 履歴 (0)
  • 最初の回答でも書きましたが、「メソッド定義の戻り値の型の部分を、動的に決定」することはできません。
    ジェネリクスもコード実行時には型がすべて決まっています(起動時にすべてが決まっているわけではないですがメソッドが呼びだされる時には決まっているハズ)。メソッドが動いている途中でメソッド定義の戻り値の型が変わることはできません。
    -
  • 欲しいものと違うのは理解していますが「A1を渡すとB1,A2を渡すとB2が返るようなメソッドはアップキャストなりインターフェースへのキャストなりすることになります。 -
  • このConvertAtoBを呼ぶとき、どういうコードになるのかわかりませんが、
    派生クラスB1 x = ConvertAtoB(派生クラスA1) と 派生クラスB2 x = ConvertAtoB(派生クラスA2) が同時に欲しいのであれば変換演算子を使う手はあります。ただし派生クラスB2 x = ConvertAtoB(基底クラスA) をやりたいならできませんが。
    -
  • 前の回答へのコメントを今読みました。ToCsもConvertAtoBもXml側に置きたかったわけですね。そういう場合もToCsもConvertAtoBもそれぞれの基底クラスを戻り値にするしかないかと思います(派生クラスでオーバーライドして実体は派生クラスを返却しますが)。 -
  • > 派生クラスB1 x = ConvertAtoB(派生クラスA1) と 派生クラスB2 x = ConvertAtoB(派生クラスA2) が同時に欲しいのであれば変換演算子を使う手はあります。
    変換演算子について調べてみたのですが、引数付きコンストラクタとの違いが分かりません。
    newしなくても変換できる、ということでしょうか?
    -
  • > 派生クラスでオーバーライドして、実体は派生クラスを返却
    なるほど!基底クラスのメソッドとしては基底クラスを、派生クラスのメソッドは各派生クラスの対象クラスを返却するという手があるのですね。早速、検証してみます。
    -

BaseXml型を継承しながら、変換できないのは何故なんでしょうか?

BaseXmlBaseXml<T>BaseXml<UserCs>は違う型です。
継承時にTにあたる型を指定していますので、BaseXmlでもBaseXml<T>でもないので型が違いキャストできません。

以下であればキャスト可能です。

BaseXml<UserCs> x = (BaseXml<UserCs>)UserXml();

BaseXml<T>への変換はできませんので、BaseXml<ProductCs>BaseXml<UserCs>も変数に入れたいのであれば、さらに親のクラス(たとえばObject)にキャストするしかありません。
ただ、そもそも親クラスへのキャストが許容できるのであれば、最初に考えられた親クラスを返却する ToCsメソッドにすればいいと思います。

気を付けて欲しいですが、ToCsメソッドにしてもConvertToXmlメソッドにしても、戻り値の型がちがうのであればたまたま名前がおなじだった別のメソッドで、共通化されていません(生成するものが違うのですから別個の機能です)。
そのあたりも踏まえて何を共通化させたいか、どんな汎用的な機能を親クラスに持たせるべきなのか、何は共通化できないのかなどを整理された方がいいように思います。クラスにどういう事をさせたいかを考えずにとりあえずでまとめようとしてしまっていませんか?


ConvertToXmlメソッドについて先に示した例と同じですが、以下の様にするのでは問題が残りますか?
(名前は ToXmlにしています。)

public abstract class BaseCs<T> {
    public abstract T ToXml();
}
public abstract class BaseXml<T>{
    public abstract T ToCs();
}
public class UserCs : BaseCs<UserXml>{
    public DateTime Birthday = DateTime.MinValue;

    public override UserXml ToXml(){
        return new UserXml(this);
    }
}

public class ProductCs : BaseCs<ProductXml>{
    public decimal Price = 0m;

    public override ProductXml ToXml(){
        return new ProductXml(this);
    }
}
public class UserXml : BaseXml<UserCs>{
    public string Birthday = string.Empty;

    public UserXml() { }
    public UserXml(UserCs c){
        this.Birthday = c.Birthday.ToString();
    }

    public override UserCs ToCs(){
        UserCs c = new UserCs();
        return c;
    }
}

public class ProductXml : BaseXml<ProductCs>{
    public string Price = string.Empty;

    public ProductXml() { }
    public ProductXml(ProductCs c){
        this.Price = c.Price.ToString();
    }

    public override ProductCs ToCs(){
        ProductCs c = new ProductCs();
        return c;
    }
}
編集 履歴 (2)
  • > BaseXmlとBaseXmlとBaseXmlは違う型です。
    すみません、こちらは読み解けませんでした。

    なるほど、BaseCs側にToXmlを組み込む方法もありますね。
    -
  • BaseCs側とBaseXml側で手分けしていたこと、
    BaseCsが基本で、BaseCsの派生としてBaseXmlを作成していたため、
    変換系をBaseXml側にまとめておこうとしていました。
    検討してみます。
    -
  • > 『BaseCsの派生としてBaseXmlを作成していたため、 変換系をBaseXml側にまとめておこうとしていました。』 なるほど、そういう事でしたか。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。