我知道这个功能不会在C#中存在,但PHP最近增加了一个功能叫做特质 ,我认为是一个有点傻,在第一,直到我开始思考它。
说我有叫一个基类Client
。 Client
有一个称为一个属性Name
。
现在我开发将由许多不同的客户可以使用可重复使用的应用程序。 所有客户的认同,客户端应该有一个名称,因此它在基类之中。
现在,客户A走过来说,他还需要跟踪客户的重量。 客户B不需要的重量,但他想追踪的高度。 客户C想要跟踪重量和高度。
随着特征,我们可以做的重量和高度功能特点:
class ClientA extends Client use TClientWeight
class ClientB extends Client use TClientHeight
class ClientC extends Client use TClientWeight, TClientHeight
现在,我能满足我所有的客户的需求,而无需添加任何额外的绒毛类。 如果我的客户回来后,说:“哦,我真的很喜欢这个功能,我能得到它吗?”,我刚更新的类定义,包括额外的特征。
你将如何在C#中做到这一点?
因为我想要的属性和任何相关的方法具体定义接口并不在这里工作,我不想重新实现它们的类的每个版本。
(通过“顾客”,我的意思是谁已聘请我作为一个开发者,而由“客户端”我指的编程类文字的人,我的每一个客户都有客户,他们希望记录信息)
Answer 1:
您可以通过使用标记的接口和扩展方法获得的语法。
先决条件:接口需要定义稍后使用的扩展方法的合同。 基本上,接口定义了能够“实行”性状的合同; 理想情况下,你添加的接口的类应该已经存在,因此是不需要额外的实现接口的所有成员。
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
}
使用这样的:
var ca = new ClientA();
ca.IsHeavierThan(10); // OK
ca.IsTallerThan(10); // compiler error
编辑:有人提出如何额外的数据可以被存储。 这也可以做一些额外的编码处理:
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;
}
}
然后,性状方法可以添加和检索数据如果“特征界面”从继承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
}
注意:通过实施IDynamicMetaObjectProvider
以及对象甚至会允许通过DLR以暴露所述动态数据,使得获得透明的附加属性与使用时dynamic
关键字。
Answer 2:
C# 语言 (至少5版)不具有的性状的支持。
然而,Scala有特点和Scala在JVM上运行(和CLR)。 因此,它不是的运行时间的问题,而只是意味着该语言。
考虑到性状,至少在斯卡拉意义上可以认为是“相当神奇编译代理方法”(它们不影响MRO,这是不同于混入用Ruby)。 在C#中实现这个行为的方法是使用接口和“大量的手工代理方法”(如组成)。
这个繁琐的过程可以用一个假设的处理器来实现(也许是自动代码生成用于通过模板部分类?),但是这并不是C#。
编码愉快。
Answer 3:
我想指出NRoles ,C#中的角色 ,其中角色类似特征的实验。
NRoles使用后的编译器重写IL和方法注入到一类。 这使您可以编写代码这样的:
public class RSwitchable : Role
{
private bool on = false;
public void TurnOn() { on = true; }
public void TurnOff() { on = false; }
public bool IsOn { get { return on; } }
public bool IsOff { get { return !on; } }
}
public class RTunable : Role
{
public int Channel { get; private set; }
public void Seek(int step) { Channel += step; }
}
public class Radio : Does<RSwitchable>, Does<RTunable> { }
其中类Radio
实现RSwitchable
和RTunable
。 在幕后, Does<R>
是不带任何成员的接口,所以基本上Radio
编译成一个空的类。 编译后的IL重写注入的方法RSwitchable
和RTunable
到Radio
,因为如果它确实从两个角色衍生然后可以使用(从另一个组件):
var radio = new Radio();
radio.TurnOn();
radio.Seek(42);
要使用radio
直接重写之前发生(即,在为其中同一组件Radio
类型声明),则不得不求助于扩展方法As<R>
radio.As<RSwitchable>().TurnOn();
radio.As<RTunable>().Seek(42);
因为编译器将不允许调用TurnOn
或Seek
直接在Radio
类。
Answer 4:
有一个学术项目,由斯特凡Reichart从软件集团的组成伯尔尼(瑞士)大学,它提供了一个真正实现性状的C#语言开发。
看看上CSharpT论文(PDF),为他做了什么,基于单编译器的完整描述。
这里是什么可以写一个例子:
trait TCircle
{
public int Radius { get; set; }
public int Surface { get { ... } }
}
trait TColor { ... }
class MyCircle
{
uses { TCircle; TColor }
}
Answer 5:
这实在是一个建议扩展卢塞罗的答案在所有存储在基类。
如何使用依赖属性呢?
这将有可能使客户端类的重量轻的,当你有很多属性未总是由每个后代设置运行时间的影响。 这是因为值存储在静态成员。
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;
}
}
Answer 6:
建设什么卢塞罗建议 ,我想出了这一点:
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.
这是不是很漂亮,我想,但无论是不是太糟糕了。
Answer 7:
性状可以在C#8通过使用默认接口的方法来实现。 Java的8引入这个原因默认接口的方法了。
使用C#8,你可以写几乎正是你在问题中提出的。 性状是由为他们的方法的默认实现的IClientWeight,IClientHeight接口来实现的。 在这种情况下,他们只返回0:
public interface IClientWeight
{
int getWeight()=>0;
}
public interface IClientHeight
{
int getHeight()=>0;
}
public class Client
{
public String Name {get;set;}
}
ClientA
和ClientB
有特点,但不执行。 ClientC仅实现IClientHeight
并返回一个异数,在这种情况下,16:
class ClientA : Client, IClientWeight{}
class ClientB : Client, IClientHeight{}
class ClientC : Client, IClientWeight, IClientHeight
{
public int getHeight()=>16;
}
当getHeight()
被调用ClientB
通过该接口,默认实现调用。 getHeight()
只能通过接口来调用。
ClientC实现IClientHeight接口,这样它自己的方法被调用。 该方法可通过类本身。
public class C {
public void M() {
//Accessed through the interface
IClientHeight clientB=new ClientB();
clientB.getHeight();
//Accessed directly or through the interface
var clientC=new ClientC();
clientC.getHeight();
}
}
此SharpLab.io例子示出了从本实施例制造的代码
许多中所描述的性状特征上的特征PHP的概述可以轻松地使用默认接口的方法来实现。 性状(接口)可被组合。 它也可以定义抽象方法来强制类来实现一定的要求。
比方说,我们希望我们的特点有sayHeight()
和sayWeight()
返回一个字符串与身高或体重的方法。 他们会需要一些方法来迫使展示类(从PHP引导被盗的术语)来实现,返回的身高和体重的方法:
public interface IClientWeight
{
abstract int getWeight();
String sayWeight()=>getWeight().ToString();
}
public interface IClientHeight
{
abstract int getHeight();
String sayHeight()=>getHeight().ToString();
}
//Combines both traits
public interface IClientBoth:IClientHeight,IClientWeight{}
客户端现在要实现泰德getHeight()
或getWeight()
方法,但并不需要了解什么say
方法。
这提供了一个装饰更清洁的方式
SharpLab.io链接此示例。
Answer 8:
这听起来像PHP的版本面向方面编程的。 有工具来帮助像PostSharp或MS团结在某些情况下。 如果你想滚你自己,用C#代码的属性注射是一种方法,或者建议的扩展方法有限的情况下。
实际上取决于你想多么复杂获得。 如果你正在试图建立复杂的东西我会看一些这些工具的帮助。
文章来源: How would you implement a “trait” design-pattern in C#?