59

この機能がC#に存在しないことは知っていますが、PHPは最近、Traitsという機能を追加しました。これは、私が考え始めるまで、最初は少しばかげていると思いました。

と呼ばれる基本クラスがあるとしClientます。Clientと呼ばれる単一のプロパティがありますName

現在、さまざまな顧客が使用する再利用可能なアプリケーションを開発しています。すべての顧客は、クライアントに名前を付ける必要があることに同意します。したがって、それは基本クラスに含まれます。

ここで、顧客Aがやって来て、クライアントの体重も追跡する必要があると言います。顧客Bは体重を必要としませんが、身長を追跡したいと考えています。顧客Cは、体重と身長の両方を追跡したいと考えています。

特性を使用すると、WeightとHeightの両方の機能特性を作成できます。

class ClientA extends Client use TClientWeight
class ClientB extends Client use TClientHeight
class ClientC extends Client use TClientWeight, TClientHeight

これで、クラスに余分な毛羽立ちを追加することなく、すべての顧客のニーズを満たすことができます。顧客が後で戻ってきて、「ああ、私はその機能が本当に好きです、私もそれを持っていてもいいですか?」と言った場合、私はクラス定義を更新して追加の特性を含めます。

C#でこれをどのように達成しますか?

プロパティと関連するメソッドの具体的な定義が必要であり、クラスのバージョンごとにそれらを再実装したくないため、インターフェイスはここでは機能しません。

(「顧客」とは、私を開発者として雇った文字通りの人を意味しますが、「クライアント」とは、プログラミングクラスを指します。各顧客には、情報を記録したいクライアントがいます)

4

8 に答える 8

58

You can get the syntax by using marker interfaces and extension methods.

Prerequisite: the interfaces need to define the contract which is later used by the extension method. Basically the interface defines the contract for being able to "implement" a trait; ideally the class where you add the interface should already have all members of the interface present so that no additional implementation is required.

public class Client {
  public double Weight { get; }

  public double Height { get; }
}

public interface TClientWeight {
  double Weight { get; }
}

public interface TClientHeight {
  double Height { get; }
}

public class ClientA: Client, TClientWeight { }

public class ClientB: Client, TClientHeight { }

public class ClientC: Client, TClientWeight, TClientHeight { }

public static class TClientWeightMethods {
  public static bool IsHeavierThan(this TClientWeight client, double weight) {
    return client.Weight > weight;
  }
  // add more methods as you see fit
}

public static class TClientHeightMethods {
  public static bool IsTallerThan(this TClientHeight client, double height) {
    return client.Height > height;
  }
  // add more methods as you see fit
}

Use like this:

var ca = new ClientA();
ca.IsHeavierThan(10); // OK
ca.IsTallerThan(10); // compiler error

Edit: The question was raised how additional data could be stored. This can also be addressed by doing some extra coding:

public interface IDynamicObject {
  bool TryGetAttribute(string key, out object value);
  void SetAttribute(string key, object value);
  // void RemoveAttribute(string key)
}

public class DynamicObject: IDynamicObject {
  private readonly Dictionary<string, object> data = new Dictionary<string, object>(StringComparer.Ordinal);

  bool IDynamicObject.TryGetAttribute(string key, out object value) {
    return data.TryGet(key, out value);
  }

  void IDynamicObject.SetAttribute(string key, object value) {
    data[key] = value;
  }
}

And then, the trait methods can add and retrieve data if the "trait interface" inherits from IDynamicObject:

public class Client: DynamicObject { /* implementation see above */ }

public interface TClientWeight, IDynamicObject {
  double Weight { get; }
}

public class ClientA: Client, TClientWeight { }

public static class TClientWeightMethods {
  public static bool HasWeightChanged(this TClientWeight client) {
    object oldWeight;
    bool result = client.TryGetAttribute("oldWeight", out oldWeight) && client.Weight.Equals(oldWeight);
    client.SetAttribute("oldWeight", client.Weight);
    return result;
  }
  // add more methods as you see fit
}

Note: by implementing IDynamicMetaObjectProvider as well the object would even allow to expose the dynamic data through the DLR, making the access to the additional properties transparent when used with the dynamic keyword.

于 2012-05-23T23:36:44.897 に答える
10

C#言語(少なくともバージョン 5 まで) は Traits をサポートしていません。

ただし、Scala には Traits があり、Scala は JVM (および CLR) 上で実行されます。したがって、これはランタイムの問題ではなく、単に言語の問題です。

少なくとも Scala の意味では、Traits は「プロキシ メソッドでコンパイルするかなりの魔法」と考えることができます ( Ruby の Mixins とは異なり、MRO には影響しません)。C# では、この動作を取得する方法は、インターフェイスと「多数の手動プロキシ メソッド」 (構成など) を使用することです。

この面倒なプロセスは仮想プロセッサ (テンプレートを介した部分クラスの自動コード生成でしょうか?) で実行できますが、それは C# ではありません。

ハッピーコーディング。

于 2012-05-23T23:30:36.953 に答える
3

これは、すべてのストレージが基本クラスにあるというLuceroの回答に対する提案された拡張です。

これに依存関係プロパティを使用するのはどうですか?

これは、すべての子孫によって常に設定されるとは限らない多くのプロパティがある場合に、実行時にクライアントクラスを軽量にする効果があります。これは、値が静的メンバーに格納されているためです。

using System.Windows;

public class Client : DependencyObject
{
    public string Name { get; set; }

    public Client(string name)
    {
        Name = name;
    }

    //add to descendant to use
    //public double Weight
    //{
    //    get { return (double)GetValue(WeightProperty); }
    //    set { SetValue(WeightProperty, value); }
    //}

    public static readonly DependencyProperty WeightProperty =
        DependencyProperty.Register("Weight", typeof(double), typeof(Client), new PropertyMetadata());


    //add to descendant to use
    //public double Height
    //{
    //    get { return (double)GetValue(HeightProperty); }
    //    set { SetValue(HeightProperty, value); }
    //}

    public static readonly DependencyProperty HeightProperty =
        DependencyProperty.Register("Height", typeof(double), typeof(Client), new PropertyMetadata());
}

public interface IWeight
{
    double Weight { get; set; }
}

public interface IHeight
{
    double Height { get; set; }
}

public class ClientA : Client, IWeight
{
    public double Weight
    {
        get { return (double)GetValue(WeightProperty); }
        set { SetValue(WeightProperty, value); }
    }

    public ClientA(string name, double weight)
        : base(name)
    {
        Weight = weight;
    }
}

public class ClientB : Client, IHeight
{
    public double Height
    {
        get { return (double)GetValue(HeightProperty); }
        set { SetValue(HeightProperty, value); }
    }

    public ClientB(string name, double height)
        : base(name)
    {
        Height = height;
    }
}

public class ClientC : Client, IHeight, IWeight
{
    public double Height
    {
        get { return (double)GetValue(HeightProperty); }
        set { SetValue(HeightProperty, value); }
    }

    public double Weight
    {
        get { return (double)GetValue(WeightProperty); }
        set { SetValue(WeightProperty, value); }
    }

    public ClientC(string name, double weight, double height)
        : base(name)
    {
        Weight = weight;
        Height = height;
    }

}

public static class ClientExt
{
    public static double HeightInches(this IHeight client)
    {
        return client.Height * 39.3700787;
    }

    public static double WeightPounds(this IWeight client)
    {
        return client.Weight * 2.20462262;
    }
}
于 2012-05-24T10:57:19.280 に答える
3

ルセロが提案したことに基づいて、私はこれを思いつきました:

internal class Program
{
    private static void Main(string[] args)
    {
        var a = new ClientA("Adam", 68);
        var b = new ClientB("Bob", 1.75);
        var c = new ClientC("Cheryl", 54.4, 1.65);

        Console.WriteLine("{0} is {1:0.0} lbs.", a.Name, a.WeightPounds());
        Console.WriteLine("{0} is {1:0.0} inches tall.", b.Name, b.HeightInches());
        Console.WriteLine("{0} is {1:0.0} lbs and {2:0.0} inches.", c.Name, c.WeightPounds(), c.HeightInches());
        Console.ReadLine();
    }
}

public class Client
{
    public string Name { get; set; }

    public Client(string name)
    {
        Name = name;
    }
}

public interface IWeight
{
    double Weight { get; set; }
}

public interface IHeight
{
    double Height { get; set; }
}

public class ClientA : Client, IWeight
{
    public double Weight { get; set; }
    public ClientA(string name, double weight) : base(name)
    {
        Weight = weight;
    }
}

public class ClientB : Client, IHeight
{
    public double Height { get; set; }
    public ClientB(string name, double height) : base(name)
    {
        Height = height;
    }
}

public class ClientC : Client, IWeight, IHeight
{
    public double Weight { get; set; }
    public double Height { get; set; }
    public ClientC(string name, double weight, double height) : base(name)
    {
        Weight = weight;
        Height = height;
    }
}

public static class ClientExt
{
    public static double HeightInches(this IHeight client)
    {
        return client.Height * 39.3700787;
    }

    public static double WeightPounds(this IWeight client)
    {
        return client.Weight * 2.20462262;
    }
}

出力:

Adam is 149.9 lbs.
Bob is 68.9 inches tall.
Cheryl is 119.9 lbs and 65.0 inches.

思ったほど良くはありませんが、悪くもありません。

于 2015-03-30T20:05:48.107 に答える
0

これは、アスペクト指向プログラミングの PHP バージョンのように思えます。場合によっては、PostSharp や MS Unity などの支援ツールがあります。独自のロールを作成する場合は、C# 属性を使用したコード インジェクションが 1 つのアプローチであるか、限られたケースで推奨される拡張メソッドです。

どれだけ複雑にしたいかによって異なります。複雑なものを構築しようとしている場合は、これらのツールのいくつかを参考にしてください。

于 2012-05-23T23:59:04.437 に答える