自从提出这个问题以来,已经过去了6年,我希望今天能有一个简单的解决方案。。但似乎并非如此。
注意:请阅读另一个问题以理解的概念
几分钟后,我尝试实现一个简单的例子,我几乎完成了。同时我仍然看到一些问题。我想知道是否有人对如何让它变得更好有想法。
使用.NET 6(代码如下(。
-
问题1:我不喜欢这样一个事实,即我们说的泛型使用
TTarget
作为User
,我们还需要传递T
ID
类型。。。。为什么通过传递User
不足以让编译器知道ID
的数据类型?示例:class UserService : IBaseDBOperation1<User, Guid>
为什么不class UserService : IBaseDBOperation1<User>
? -
问题2:我知道现在我们可以用带有代码的方法来定义接口,但为什么我们仍然需要用这两种数据类型来定义变量类型,而仅仅使用
var
是不够的?好吧,我们可以使用var
,但这些方法是不可见的。而不是:IBaseDBOperation1<User, Guid> serviceU1 = new UserService();
。。。。。。。。var serviceU2 = new UserService();
。。。。。。第二个变量将看不到其他方法。
最后一点:如果C#允许我们用多个其他抽象类扩展一个类,一切都会变得容易得多。。。。(截至今天,我们仅限于1(。
目标:完成6年前提出的问题中的要求。。。。换句话说。。。。避免复制/粘贴;注入/关联/注册/定义";多于一个";操作类";进入服务。。。。那些";操作类";将在多个不同的服务中大量重复使用。。。。我确实想有一个";干净/漂亮";这种设置方式,但同时,消费者也不应该担心"更低/更深";杠杆继承泛型。
代码
public abstract class BaseDBEntry<T> where T : struct
{
protected BaseDBEntry()
{
CreatedOn = DateTime.Now;
}
public T Id { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? DeletedOn { get; set; }
}
public class User : BaseDBEntry<Guid>
{
public User() { Id = Guid.NewGuid(); }
public string Name { get; set; }
}
public class Color : BaseDBEntry<long>
{
public Color() { Id = DateTime.Now.Ticks; }
public string RGB { get; set; }
}
服务
public interface IBaseDBOperation1<in TTarget, out T>
where TTarget : BaseDBEntry<T> where T : struct
{
public bool IsValid(TTarget model) { return true; }
T GiveMeId(TTarget model) { return model.Id; }
}
public interface IBaseDBOperation2<in TTarget, T>
where TTarget : BaseDBEntry<T> where T : struct
{
public bool IsValidToDoSomethingElse(TTarget model) { return false; }
}
public class UserService : IBaseDBOperation1<User, Guid>, IBaseDBOperation2<User, Guid> { }
public class ColorService : IBaseDBOperation1<Color, long>, IBaseDBOperation2<Color, long> { }
消费者
public class Consumer
{
public void Run()
{
IBaseDBOperation1<User, Guid> serviceU1 = new UserService();
IBaseDBOperation2<User, Guid> serviceU2 = new UserService();
var u = new User { Name = "Carl" };
var resU1 = serviceU1.IsValid(u);
var resU2 = serviceU1.GiveMeId(u);
var resU3 = serviceU2.IsValidToDoSomethingElse(u);
var serviceU3 = new UserService();
//serviceU3.XXXXX() --> has no information about the methods we need
IBaseDBOperation2<Color, long> serviceC1 = new ColorService();
var c = new Color { RGB = "#FFFFFF" };
var resC1 = serviceC1.IsValidToDoSomethingElse(c);
var adasda = "";
}
}
var consumer = new Consumer();
consumer.Run();
我将从小注释开始-请尽量遵循标准命名惯例,在这种情况下是:
接口名称以大写
I
开头。
至于问题:
第1期:我不喜欢这样一个事实,即我们说的泛型使用
TTarget
作为User
,我们还需要传递T
ID
类型。
在过去的6年里,这里没有太大的变化,interface BaseDBOperation1<TTarget, T>..
仍然需要2个泛型类型参数,并且你仍然可以有一个带有一个类型参数的接口,即interface BaseDBOperation1<TTarget>
,这对编译器来说是不明确的(所以添加interface BaseDBOperation1<TTarget>
将成为一个突破性的变化,如果这些类作为库分发,这是一个问题(。
类似这样的事情可能可以通过更高级的类型或类似的语言功能来实现,但ATM在C#中不可用。
要跟踪的相关问题:
- 一类泛型方法/一类多态性/Rank-N类型
- Generics上的高级多态性/Generics
- "不透明的";参数
- 接口的存在类型和抽象类型
- 部分类型推理
第2期:。。。第二个变量将看不到其他方法。
这是按设计的(默认接口方法草案规范(:
注意,类不会从其接口继承成员;该功能不会改变:
interface IA
{
void M() { WriteLine("IA.M"); }
}
class C : IA { } // OK
new C().M(); // error: class 'C' does not contain a member 'M'
为了调用接口中声明和实现的任何方法,变量必须是接口的类型
即对于var serviceU2 = new UserService();
,您需要转换到相应的接口:
var resU1 = ((BaseDBOperation1<User, Guid>)serviceU2).IsValid(u);
这种行为的另一个原因可能类似于所谓的脆性/脆性基类问题。
就我个人而言,无论是从概念上还是由于一些角落的情况(例如这个(,我都不太喜欢这个功能。
至于实现这种功能和减少手动编写代码的方法(如果你有很多这样的存储库(,你可以考虑使用源代码生成器生成一些编译时代码,但这绝对不是一个容易的选择。至少是第一次。
以下内容适合您吗?本质上使用户和颜色成为服务操作的持有者。
https://dotnetfiddle.net/zsjZfQ
using System;
var user = new User();
var color = new Color();
Console.WriteLine(user.DbOperation1.IsValid());
Console.WriteLine(user.DbOperation2.IsValidToDoSomethingElse());
Console.WriteLine(user.DbOperation3.ThisIsOnlyAvailableToSome());
Console.WriteLine(color.DbOperation1.IsValid());
Console.WriteLine(color.DbOperation2.IsValidToDoSomethingElse());
var userAsBaseDbEntry = (BaseDBEntry<Guid>)user;
Console.WriteLine(userAsBaseDbEntry.DbOperation1.IsValid());
Console.WriteLine(userAsBaseDbEntry.DbOperation2.IsValidToDoSomethingElse());
public abstract class BaseDBEntry<T> where T : struct
{
public abstract IDbOperation1<T> DbOperation1 { get; init; }
public abstract IDbOperation2<T> DbOperation2 { get; init; }
public T Id { get; init; }
public DateTime CreatedOn { get; init; } = DateTime.Now;
public DateTime? DeletedOn { get; init; }
}
public class User : BaseDBEntry<Guid>
{
public string Name { get; init; }
override public sealed IDbOperation1<Guid> DbOperation1 { get; init; }
override public sealed IDbOperation2<Guid> DbOperation2 { get; init; }
public IDbOperation3 DbOperation3 { get; }
public User()
{
DbOperation1 = new DbOperation1Impl<Guid>(this);
DbOperation2 = new DbOperation2Impl<Guid>(this);
DbOperation3 = new DbOperation3Impl(this);
Id = Guid.NewGuid();
}
}
public interface IDbOperation3
{
bool ThisIsOnlyAvailableToSome();
}
public class DbOperation3Impl : IDbOperation3
{
private readonly BaseDBEntry<Guid> _entry;
public DbOperation3Impl(BaseDBEntry<Guid> entry)
{
_entry = entry;
}
public bool ThisIsOnlyAvailableToSome() => !_entry.DbOperation1.IsValid();
}
public class Color : BaseDBEntry<long>
{
override public sealed IDbOperation1<long> DbOperation1 { get; init; }
override public sealed IDbOperation2<long> DbOperation2 { get; init; }
public string Rgb { get; init; }
public Color()
{
DbOperation1 = new DbOperation1Impl<long>(this);
DbOperation2 = new DbOperation2Impl<long>(this);
Id = DateTime.Now.Ticks;
}
}
public interface IDbOperation1<T> where T : struct
{
bool IsValid();
}
public interface IDbOperation2<T> where T : struct
{
bool IsValidToDoSomethingElse();
}
class DbOperation1Impl<T> : IDbOperation1<T> where T : struct
{
private readonly BaseDBEntry<T> _entry;
public DbOperation1Impl(BaseDBEntry<T> entry)
{
_entry = entry;
}
public bool IsValid() => _entry.CreatedOn < DateTime.Now;
}
class DbOperation2Impl<T> : IDbOperation2<T> where T : struct
{
private readonly BaseDBEntry<T> _entry;
public DbOperation2Impl(BaseDBEntry<T> entry)
{
_entry = entry;
}
public bool IsValidToDoSomethingElse() => _entry.DeletedOn != null;
}