我对显式强制转换运算符的使用是否合理?


24

我有一个大对象:

class BigObject{
    public int Id {get;set;}
    public string FieldA {get;set;}
    // ...
    public string FieldZ {get;set;}
}

还有一个专门的,类似于DTO的对象:

class SmallObject{
    public int Id {get;set;}
    public EnumType Type {get;set;}
    public string FieldC {get;set;}
    public string FieldN {get;set;}
}

我个人发现了将BigObject显式转换为SmallObject的概念-知道这是一种单向丢失数据的操作-非常直观且易读:

var small = (SmallObject) bigOne;
passSmallObjectToSomeone(small);

它使用显式运算符实现:

public static explicit operator SmallObject(BigObject big){
    return new SmallObject{
        Id = big.Id,
        FieldC = big.FieldC,
        FieldN = big.FieldN,
        EnumType = MyEnum.BigObjectSpecific
    };
}

现在,我可以SmallObjectFactory使用FromBigObject(BigObject big)方法创建一个类,该类将执行相同的操作,将其添加到依赖项注入中,并在需要时调用它……但是对我来说,这似乎更加复杂和不必要。

PS我不确定这是否相关,但是将可以OtherBigObject将其转换为SmallObject,并设置为different EnumType


4
为什么不构造函数?
edc65

2
还是静态工厂方法?
布赖恩·戈登

为什么需要工厂类或依赖项注入?您在这里做了错误的二分法。
user253751

1
@immibis-因为我不知道@Telastyn提出了什么:.ToSmallObject()方法(或GetSmallObject())。一时的失误-我知道我的想法有问题,所以我问过你们:)
Gerino 2015年

3
这听起来像是ISmallObject接口的完美用例,该接口仅由BigObject实现,以提供对有限数量的大量数据/行为的访问。特别是与@Telastyn的ToSmallObject方法思想结合使用时。
Marjan Venema,2015年

Answers:


0

在我的拙见中,没有其他答案是对的。在这个stackoverflow问题中,投票最高的答案认为映射代码应放在域之外。要回答您的问题,不-您对强制转换运算符的使用不是很好。我建议您做一个映射服务,该服务位于您的DTO和您的域对象之间,或者您可以为此使用automapper。


这是一个了不起的主意。我已经有Automapper了,所以这很容易。我唯一的问题是:难道不应该有BigObject和SmallObject有某种联系的痕迹吗?
Gerino'5

1
不,除了映射服务之外,将BigObject和SmallObject进一步耦合在一起没有任何优势。
Esben Skov Pedersen

7
真?自动映射器是您解决设计问题的解决方案吗?
Telastyn

1
BigObject可映射到SmallObject,在经典的OOP意义上它们并不是真正相互关联的,而代码则反映了这一点(两个对象都存在于域中,映射能力与其他许多映射属性一起设置)。它确实删除了可疑的代码(不幸的是,我重写了操作符),使模型整洁(其中没有方法),是的,这似乎是一种解决方案。
Gerino'5

2
@EsbenSkovPedersen此解决方案就像使用推土机挖一个洞来安装邮箱一样。幸运的是,OP还是想挖出院子,所以在这种情况下,推土机可以工作。但是,我一般不会推荐此解决方案。
尼尔

81

太好了 我使用的代码巧妙地完成了这一工作,并导致了混乱。毕竟,你会希望能够只分配BigObject到一个SmallObject变量,如果对象是相关的,足以扮演他们。但是,它不起作用-如果尝试进行编译,则会出现编译器错误,因为就类型系统而言,它们是无关紧要的。对于铸造操作人员来说,制造新物体也令人反感。

我会推荐一种.ToSmallObject()方法。关于实际发生的事情和冗长的内容,我们会更加清楚。


18
道... ToSmallObject()似乎是最明显的选择。有时最明显的是最难以捉摸的;)
Gerino

6
mildly distasteful轻描淡写。不幸的是,这种语言使这种事情看起来像打字机。除非他们自己编写,否则没人会猜测这是一个实际的对象转换。在一个单人团队中,很好。如果您与任何人合作,最好的情况是浪费时间,因为您必须停下来弄清楚这是否真的是演员,还是这是疯狂的转变之一。
肯特A.

3
@Telastyn同意这不是最令人讨厌的代码味道。但是,大多数程序员将对对象的隐藏隐藏在操作中,大多数程序员都认为这是向编译器发出的将同一对象视为不同类型的指令,这对任何需要在您之后处理您的代码的人都不感兴趣。:)
肯特A.

4
为+1 .ToSmallObject()。几乎不应该覆盖运算符。
ytoledano

6
@dorus-至少在.NET中,Get意味着返回一个现有的东西。除非您重写了对小对象的操作,否则两次Get调用将返回不相等的对象,从而造成混乱/错误/ wtfs。
Telastyn

11

虽然我可以看到您为什么需要使用SmallObject,但是我会以不同的方式处理该问题。我处理此类问题的方法是使用Facade。其唯一目的是封装BigObject并仅提供特定成员。这样,它是同一实例上的新接口,而不是副本。当然,你可能需要执行复制,但我会建议您通过对组合的目的与门面(例如创建了一个方法,这样做return new SmallObject(instance.Clone()))。

Facade具有许多其他优点,即确保程序的某些部分只能使用通过Facade提供的成员,从而有效地保证了它无法利用不应了解的内容。除此之外,它还有一个巨大的优势,那就是您可以BigObject在将来的维护中更灵活地进行更改,而不必担心在整个程序中如何使用它。只要您可以某种形式模仿旧的行为,就可以SmallObject像以前一样进行工作,而不必在BigObject将要使用的所有地方更改程序。

请注意,这BigObject并不取决于SmallObject而是相反(正如我的拙见)。


您提到的Facade具有将字段复制到新类的唯一优点是避免了复制(除非对象具有大量的字段,否则这可能不是问题)。另一方面,与静态转换方法不同,它的缺点是每次需要转换为新类时都必须修改原始类。
2015年

@Doval我想这就是重点。您不会将其转换为新的类。如果需要的话,您将创建另一个外观。对BigObject所做的更改仅应应用于Facade类,而不是在使用它的任何地方进行。
尼尔

这种方法与Telastyn的答案之间的一个有趣的区别是,生成的责任SmallObject在于SmallObject还是BigObject。默认情况下,此方法会强制 SmallObject避免依赖的私有/受保护成员BigObject。我们可以更进一步,并SmallObject通过使用ToSmallObject扩展方法来避免依赖于私有/受保护成员。
布赖恩

@Brian您可能会那样混乱BigObject。如果您想做类似的事情,您可以担保在中创建ToAnotherObject扩展方法BigObject。这些不应该成为问题,BigObject因为大概它已经足够大了。它还允许您BigObject从其依赖项的创建中分离出来,这意味着您可以使用工厂等。另一种方法强烈地耦合BigObjectSmallObject。在这种情况下可能会很好,但根据我的拙见,这并不是最佳做法。
尼尔2015年

1
@Neil实际上,Brian解释错了,但他对的-扩展方法确实摆脱了耦合。它不再与之BigObject耦合SmallObject,它只是一个静态方法,该方法接受参数BigObject并返回SmallObject。扩展方法实际上只是一种语法糖,可以更好地调用静态方法。扩展方法是不是部分BigObject,它是一个完全独立的静态方法。实际上,这是扩展方法的很好用,特别是对于DTO转换非常方便。
罗安2015年

6

有一个非常严格的约定,对可变引用类型的强制转换是保留身份的。因为在将源类型的对象分配给目标类型的引用的情况下,系统通常不允许用户定义的强制转换运算符,所以只有少数情况下用户定义的强制转换操作对于可变引用是合理的类型。

我建议作为一个要求,x=(SomeType)foo;稍后再加上y=(SomeType)foo;,两个强制转换都应用于同一对象x.Equals(y),即使该对象在两个强制转换之间进行了修改,也应该永远永远正确。如果例如一个对象具有一对不同类型的对象,每个对象持有另一个对象的不可变引用,并且将一个对象转换为另一种类型将返回其配对实例,则可能会出现这种情况。它也可以与用作可变对象包装器的类型一起使用,前提是被包装的对象的标识是不可变的,并且如果两个相同类型的包装器包装相同的集合,它们将报告自己相等。

您的特定示例使用了可变的类,但没有保留任何形式的身份。因此,我建议这不是铸造操作员的适当用法。


1

可能没关系。

您的示例存在一个问题,就是您使用了类似示例的名称。考虑:

SomeMethod(long longNum)
{
  int num = (int)longNum;
  /* ... */

现在,当你有个好主意什么int的含义时,intto 的隐式转换long和from的显式long转换int都是可以理解的。这也是可以理解如何3成为3和只是另一种方式与工作3。这是可以理解的,这int.MaxValue + 1在检查的上下文中将如何失败是可以理解的。即使int.MaxValue + 1在不受约束的情况下如何实现结果int.MinValue也不是最难的事情。

同样,当您隐式转换为基本类型或显式转换为派生类型时,任何知道继承如何工作,发生什么情况以及结果将是什么(或失败的方法)的人都可以理解。

现在,对于BigObjectSmallObject,我对这种关系的工作方式一无所知。如果您的实类型使得转换关系明显,那么转换可能确实是一个好主意,尽管在很多时候,也许是绝大多数,如果是这种情况,那么它应该反映在类层次结构和普通的基于继承的转换就足够了。


实际上,它们只不过是所提供的内容而已,但例如BigObject可能描述了Employee {Name, Vacation Days, Bank details, Access to different building floors etc.},也SmallObject可能是MoneyTransferRecepient {Name, Bank details}。从EmployeeMoneyTransferRecepient都有直接的转换,没有理由向银行应用程序发送超出需要的更多数据。
Gerino'5
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.