是否有一种巧妙的方法来指定一个类必须包含一个工厂方法,该方法返回与覆盖抽象方法的类相同类型的对象? (编辑:或者正如Johnathon Sullinger更有说服力地说, [...]有一个基类强制执行子类来实现一个返回子类本身实例的方法,并且不允许返回从基类继承的任何其他Type的实例。)
例如,如果我有两个类SimpleFoo : BaseFoo
和FancyFoo : BaseFoo
,我可以定义一个抽象工厂方法public TFoo WithSomeProp(SomeProp prop)
,其中TFoo
是一个类型参数,以某种方式通过抽象方法定义固定到覆盖它的特定类?
我希望编译时保证
WithSomeProp
中的具体SomeFoo : BaseFoo
方法定义只能生成SomeFoo
个。如果静态抽象方法定义是合法的,那么以下(伪语法)方法扩展可能最能表达这种需求:
public static abstract TFoo WithSomeProp<TFoo>(this TFoo source, SomeProp prop)
where TFoo : BaseFoo;
我不认为在C#中这是可能的。
或至少某种方式在抽象方法中参数化返回类型,例如
public abstract TFoo WithSomeProp<TFoo>(SomeProp prop)
where TFoo : BaseFoo;
这不会阻止FancyFoo.WithSomeProp
返回SimpleFoo
,但确定。
这个抽象方法本身似乎有效,但我的具体定义失败了:
public override SimpleFoo WithSomeProp(SomeProp prop)
{
return new SimpleFoo(this.SomeOtherProp, ..., prop);
}
带警告
找不到合适的方法来覆盖
在我看来,在抽象方法中指定类型参数不允许在这些定义的覆盖中修复它们,而是指定“应该存在具有类型参数的方法”。
现在我只有public abstract BaseFoo WithSomeProp(SomeProp prop);
。
答案 0 :(得分:4)
这听起来像你想要做的,有一个基类强制执行一个子类来实现一个返回子类本身实例的方法,并且不允许返回从base继承的任何其他Type的实例类。不幸的是,据我所知,这不是你能做的事情。
但是,您可以强制子类指定它对基类的类型,以便基类可以强制返回值必须是子类指定的类型。 / p>
例如,给定一个名为BaseFactory
和BaseFactory<T>
的基类,我们可以创建一个抽象类,要求子项指定父项,创建方法返回什么类型。我们添加了BaseFactory
类,因此我们可以将T
限制为仅BaseFactory
的子类。
如果有帮助,我会在下面留下原来的答案,但经过一番思考后,我想我已经为你找到了更好的解决方案。
您仍然需要基类来获取定义子类型是什么的泛型参数。然而,现在的区别在于基类具有静态创建方法而不是实例方法。您可以使用此创建方法来创建子类的新实例,并可选择在返回之前调用回调以在新实例上配置属性值。
public abstract class BaseFactory { }
public abstract class BaseFactory<TImpl> : BaseFactory where TImpl : BaseFactory, new()
{
public static TImpl Create(Action<TImpl> itemConfiguration = null)
{
var child = new TImpl();
itemConfiguration?.Invoke(child);
return child;
}
}
然后,您可以正常创建子类,而不必担心覆盖任何方法。
public class Foo : BaseFactory<Foo>
{
public bool IsCompleted { get; set; }
public int Percentage { get; set; }
public string Data { get; set; }
}
public class Bar : BaseFactory<Bar>
{
public string Username { get; set; }
}
然后你会像这样使用工厂。
class Program
{
static void Main(string[] args)
{
// Both work
Bar bar1 = Bar.Create();
Foo foo1 = Foo.Create();
// Won't compile because of different Types.
Bar bar2 = Foo.Create();
// Allows for configuring the properties
Bar bar3 = Bar.Create(instanceBar => instanceBar.Username = "Jane Done");
Foo foo2 = Foo.Create(instanceFoo =>
{
instanceFoo.IsCompleted = true;
instanceFoo.Percentage = 100;
instanceFoo.Data = "My work here is done.";
});
}
BaseFactory<T>
将负责创建TImpl
的新实例并将其还原。
public abstract class BaseFactory { }
public abstract class BaseFactory<TImpl> : BaseFactory where TImpl : BaseFactory
{
public abstract TImpl WithSomeProp();
}
现在,您的子类可以创建,并从BaseFactory<T>
继承,告诉基类T
代表自己。这意味着孩子只能自己回归。
public class Foo : BaseFactory<Foo>
{
public override Foo WithSomeProp()
{
return new Foo();
}
}
public class Bar : BaseFactory<Bar>
{
public override Bar WithSomeProp()
{
return new Bar();
}
}
然后你会像:
一样使用它class Program
{
static void Main(string[] args)
{
var obj1 = new Bar();
// Works
Bar obj2 = obj1.WithSomeProp();
// Won't compile because obj1 returns Bar.
Foo obj3 = obj1.WithSomeProp();
}
}
如果你真的想确保指定的泛型与拥有的Type相同,你可以改为使WithSomeProp
成为受保护的方法,这样子类只能看到它。然后,在基类上创建一个可以进行类型检查的公共方法。
public abstract class BaseFactory { }
public abstract class BaseFactory<TImpl> : BaseFactory where TImpl : BaseFactory
{
protected abstract TImpl WithSomeProp();
public TImpl Create()
{
Type myType = this.GetType();
if (typeof(TImpl) != myType)
{
throw new InvalidOperationException($"{myType.Name} can not create instances of itself because the generic argument it provided to the factory is of a different Type.");
}
return this.WithSomeProp();
}
}
public class Foo : BaseFactory<Foo>
{
protected override Foo WithSomeProp()
{
return new Foo();
}
}
public class Bar : BaseFactory<Bar>
{
protected override Bar WithSomeProp()
{
return new Bar();
}
}
class Program
{
static void Main(string[] args)
{
var obj1 = new Bar();
// Works
Bar obj2 = obj1.Create();
// Won't compile because obj1 returns Bar.
Foo obj3 = obj1.Create();
}
}
现在,如果您创建一个将类型传递为T
的子类,则基类将捕获它并抛出异常。
// Throws exception when BaseFactory.Create() is called, even though this compiles fine.
public class Bar : BaseFactory<Foo>
{
protected override Foo WithSomeProp()
{
return new Foo();
}
}
不确定这是否能让你得到你想要的东西,但我认为这可能是你能得到的最接近的东西。
答案 1 :(得分:1)
受Johnathon Sullinger的精彩回答启发,这是我结束的代码。 (我添加了一个主题。)
我将类型参数T
与类定义一起传递,并约束T : Base<T>
。
BaseHyperLink.cs:
public abstract class BaseHyperLink<THyperLink> : Entity<int>
where THyperLink : BaseHyperLink<THyperLink>
{
protected BaseHyperLink(int? id, Uri hyperLink, ContentType contentType, DocumentType documentType)
: base(id)
{
this.HyperLink = hyperLink;
this.ContentType = contentType;
this.DocumentType = documentType;
}
public Uri HyperLink { get; }
public ContentType ContentType { get; }
public DocumentType DocumentType { get; }
public abstract THyperLink WithContentType(ContentType contentType);
}
SharedHyperLink.cs:
public sealed class SharedHyperLink : BaseHyperLink<SharedHyperLink>
{
public SharedHyperLink(int? id, Uri hyperLink, ContentType contentType, DocumentType documentType)
: base(id, hyperLink, contentType, documentType)
{
}
public override SharedHyperLink WithContentType(ContentType contentType)
{
return new SharedHyperLink(this.Id, contentType, this.DocumentType);
}
}
MarkedHyperLink.cs:
public sealed class MarkedHyperLink : BaseHyperLink<MarkedHyperLink>
{
public MarkedHyperLink(int? id, Uri hyperLink, ContentType contentType, DocumentType documentType, Mark mark)
: base(id, hyperLink, contentType, documentType)
{
this.Mark = mark;
}
public Mark Mark { get; }
public override MarkedHyperLink WithContentType(ContentType contentType)
{
return new MarkedHyperLink(this.Id, contentType, this.DocumentType, this.Mark);
}
}