有没有办法定义我的类,以便该类的调用者获得编译时错误,除非他们指定了类的每个属性,并且参数传递的附加约束被命名?
我们说我有这个界面:
public interface IPerson
{
string FirstName {get;}
string MiddleName { get; }
string LastName { get;}
}
这些课程:
public class PersonNeedAllProperties : IPerson
{
public string FirstName { get; private set;} // ideally readonly
public string MiddleName { get; private set;}
public string LastName { get; private set;}
public PersonNeedAllProperties(
string firstName,
string lastName,
string middleName)
{
this.FirstName = firstName;
this.MiddleName = middleName;
this.LastName = lastName;
}
}
public class PersonCanNamePropertiesButNotSadlyNotRequired : IPerson
{
public string FirstName { get; set;} // ideally readonly
public string MiddleName { get; set;}
public string LastName { get; set;}
}
然后这些imho的问题如下:
var p1 = new PersonNeedAllProperties("lastName", "firstName", "middle");
// woops wrong order because not named.
var p2 = new PersonCanNamePropertiesButNotSadlyNotRequired()
{
FirstName = "firstName",
LastName = "lastName"
}; // woops forgot all about the middlename.
有没有办法让一个简单的POCO对象具有类似于第二人的初始化,但这要求所有属性被命名?
以上是上述的小提琴:https://dotnetfiddle.net/gUk8eT#
答案 0 :(得分:2)
回答你的问题不,这是不可能的。
你要求能够强制做两件事:
这些事情可以单独完成,但不能合并,因为这样做的方法截然相反,如下所述。
a)要强制设置所有属性,您需要将它们作为构造函数中的输入。
b)要强制按名称设置属性,不能在构造函数中指定它们。
附注:可以为构造函数提供命名参数,但不可能强制这些参数var person = new Person(firstName: 'John', lastName: 'Doe', middleName: 'A')
您可以获得的最接近的是标记您的属性readonly(私有集),并且只能在您的PersonNeedAllProperties
类中的构造函数中设置它们。
可能是可行替代方案的东西称为Builder Pattern。 这将有一个额外的类负责构造您的对象,但您仍然无法获得编译时错误,只有运行时。
答案 1 :(得分:2)
几乎强制执行此操作的可怕方式。不是 name 而是 type 。我不推荐它。但有一种可怕的方法可以做到这一点。我提到它太可怕了吗?
"计算机科学中的任何问题都可以通过添加另一层间接来解决。 - 惠勒?
将每个属性设为自己的 distinct type 。现在你不能以错误的顺序传递它们,否则编译器会告诉你传递了无效的类型。
public class FirstName
{
string Value { get; set; }
}
public class MiddleName
{
string Value { get; set; }
}
public class LastName
{
string Value { get; set; }
}
public interface IPerson
{
FirstName FirstName {get;}
MiddleName MiddleName { get; }
LastName LastName { get;}
}
public class PersonNeedAllProperties : IPerson
{
public FirstName FirstName { get; private set;} // ideally readonly
public MiddleName MiddleName { get; private set;}
public LastName LastName { get; private set;}
public PersonNeedAllProperties(
FirstName firstName,
MiddleName lastName,
LastName middleName)
{
this.FirstName = firstName;
this.MiddleName = middleName;
this.LastName = lastName;
}
}
答案 2 :(得分:2)
这可以通过嵌套类来实现:
public interface IPerson
{
string FirstName { get; }
string MiddleName { get; }
string LastName { get; }
}
public class Person : IPerson
{
public string FirstName { get; private set; }
public string MiddleName { get; private set; }
public string LastName { get; private set; }
//Make sure the parameterless constructor is private
private Person() { }
private Person(string first, string middle, string last)
{
this.FirstName = first;
this.MiddleName = middle;
this.LastName = last;
}
public class Builder
{
private Person person = new Person();
public Builder WithFirstName(string first)
{
person.FirstName = first;
return this;
}
public Builder WithMiddleName(string middle)
{
person.MiddleName = middle;
return this;
}
public Builder WithLastName(string last)
{
person.LastName = last;
return this;
}
public IPerson Build()
{
if (person.FirstName != null
&& person.MiddleName != null
&& person.LastName != null)
return person;
throw new Exception("Cannot build person because reasons...");
}
}
}
然后按如下方式使用:
var person = new Person.Builder()
.WithFirstName("Rob")
.WithMiddleName("<I have no middle name>")
.WithLastName("Janssen")
.Build();
您可以按照自己喜欢的顺序使用With...
方法:
var person = new Person.Builder()
.WithLastName("Janssen")
.WithFirstName("Rob")
.WithMiddleName("<I have no middle name>")
.Build();
编辑:废话;我没有注意到编译时错误的其他要求。
然而,这会“强制”您设置所有3个必填字段,并“强制”您使用With...
方法“命名”字段,这使得很难混淆值并且还允许您按所需顺序指定值。它还会阻止您自己实例化Person
并允许您拥有私有设置器(例如“只读”属性)并保持接口完好无损。这里缺少唯一的东西是编译时错误;你不会在这里得到一个。也许Code Contracts可以帮助该部门,但至少需要一些“团队协调”(例如,每个人都需要install an extension并拥有static checking enabled);如果可以通过代码合同完成,我也不能100%确定。
答案 3 :(得分:0)
不,接口的设计不符合这种方式。
接口确保实现它的类将具有在接口中声明的所有元素。但是没有就如何实施它们发表任何声明。
您可以使用自定义构造函数的抽象类来完成您想要的任务。
public abstract class Person
{
public abstract string FirstName {get; protected set; }
public abstract string MiddleName { get; protected set; }
public abstract string LastName { get; protected set; }
public Person(string firstName, string middleName, string lastName){
FirstName = firstName;
MiddleName = middleName;
LastName = lastName;
}
}
public class PersonNeedAllProperties : Person
{
public override string FirstName{get; protected set;}
public override string MiddleName{get; protected set;}
public override string LastName{get; protected set;}
public PersonNeedAllProperties(
string firstName,
string lastName,
string middleName)
: base(firstName, lastName, middleName)
{
}
}
自定义构造函数强制您完成子类中的名称。
您可以将此答案与John Gardner之一结合使用类而不是字符串作为名称(firstName,middleName,lastName)来模拟命名参数。
您需要了解此方法的局限性,在C#中,您无法从多个类继承,因此如果您决定实施此解决方案,则需要牢记此限制。