【问题标题】:Generic Identifier handling implementations通用标识符处理实现
【发布时间】:2020-04-14 05:38:05
【问题描述】:

由于我的应用程序必须使用大量标识符,因此我考虑使用 Id<T> 类对它们进行抽象。这个类可以像 Guid 一样工作,它有一个 CreateNew() 方法等等,但是会根据 T 的类型创建一个不同的 Id。

例子:

var id = Id<Individual>.CreateNew(); // == "INDIVIDUAL-12345"
var id = Id<Organization>.CreateNew(); // == "ORGANIZATION-67890"

现在,IndividualOrganization 实现了接口 IUser

现在我希望我的 Id 处理不同的实现,但这不起作用。

即考虑以下类,使用 Id:

class Transaction{
    private Id<IUser> _sender;
    private Id<IUser> _receiver;
    private Amount _amount;

    public Transaction(Id<IUser> sender, Id<IUser> receiver, Amount amount {...}
}

我希望能够致电:

new Transaction(Id<Individual>.CreateNew(), Id<Organization>.CreateNew(), new Amount());

但这是不可能的,因为构造函数要求类型为Id&lt;IUser&gt;。你能想到任何解决方法吗?


更新

感谢您到目前为止的回答。
Id&lt;IUser&gt; 只是一个示例(IUser 的内容在这里并不重要),我想支持许多对象的标识符(10/15,更多在未来?)。

Id&lt;T&gt; 使用静态类作为助手,称为 TypesMatcher,其工作是将类型转换为前缀(即“个人”、“组织”、“事务”..)。

Id&lt;T&gt;.CreateNew() 或多或少看起来像这样:

    [Pure]
    public static Id<T> CreateNew()
    {
        if (!TypesMatcher.IsSupported<T>()) throw new Exception($"Type {typeof(T)} is not supported.");

        string prefix = TypesMatcher.GetPrefixForType<T>();
        string body = Guid.NewGuid().ToString();

        return new Id<T>($"{prefix}-{body}");
    }

【问题讨论】:

  • 请分享Id&lt;T&gt;声明
  • 分享IUser接口定义

标签: c# generics


【解决方案1】:

我不确定我是否得到了你的目标/你试图在这里解决的问题。

泛型是关于允许使用您的代码的人放入任何类,不会破坏编译时类型检查。在泛型之前,我们必须为每种类型创建一个特殊的集合,或者使用 Object 作为类型。一个是工作量不合适,有很多陷阱。另一个刚刚破坏了类型安全。这只是我们最喜欢留在前泛型历史中的一团糟。但我仍然觉得了解泛型存在的原因、它们要解决的问题以及无法解决的问题很重要。

当您使用接口函数访问值时,它们只能在运行时解析。虽然它们可能是类中的编译时常量,但编译器无法确定它,因为它们是由函数在运行时分发的。但是,似乎可以使用Literal Keyword 向接口添加一种编译时间常量。但我不确定该接口的实现者是否可以添加一个 tiffrent“typeID”值。我只是在写作时读到了这件事。

就个人而言,您分享的代码让我想起了工厂模式的很多变体/不完全是单例模式。一种工厂方法,其中相同的输入(相同的集合、类型)将产生相同的输出。但我现在找不到示例。

使用共享代码,我唯一能看到被通用化的是事务方法:

public <senderType, ReceiverType> Transaction (senderType sender, ReceiverType receiver, Amount amount) { }

但同样,我不确定您尝试做什么以及您尝试解决的具体问题。

【讨论】:

    【解决方案2】:

    泛型无法处理任何继承的概念。 协方差仅在接口上可用。

    如果你有一个协变接口

      interface IID<out IUser>
      {
            IID<IUser> CreateNew();
      }
    

    你用这个代替ID&lt;User&gt;,个人和组织实现IUser, 和ID&lt;Invdidual&gt; 实现IID&lt;Individual&gt;ID&lt;Organization&gt; 实现IID&lt;Organization&gt;

    那么你就可以做这个作业了:

      IID<IUser> id = = ID<Individual>.CreateNew();
    

    或将其用作参数。

    但另一方面,以这种方式使用它:

      IID<Individual> id =  (IID<Individual>)ID<Individual>.CreateNew();
    

    你需要一个演员表。

    【讨论】:

      【解决方案3】:

      我会先查看您想要的语法:Id&lt;Individual&gt;.CreateNew()

      这意味着应该有一个Id&lt;T&gt; 类和一个静态CreateNew 方法。

      static 方法不能被覆盖,因此使其可扩展的一个好方法是将行为注入方法内部。

      因此,对于每个实体类型,我们可以创建一个类似于 SomeEntityId 的类,该类派生自 Id&lt;SomeEntity&gt;。该类应包含用于生成 id 的自定义逻辑。然后我们可以将它们注册到 DI 容器中。然后在静态CreateNew 方法中,我会从容器中获取基于T 的注册类的实例。这样你就可以注入基于T的自定义逻辑。

      我还将Transaction 类更改为依赖IId&lt;IUser&gt;,这样我可以创建一个Variant Generic InterfaceIId&lt;T&gt;

      示例

      public interface IId<out T> where T : IUser
      {
          string Value { get; }
      }
      public class Id<T> : IId<T> where T : IUser
      {
          public string Value { get; protected set; }
          protected virtual Id<T> GenerateId() => new Id<T>() { Value = $"{Guid.NewGuid()}" };
          public static Id<T> CreateNew() => Container.GetInstance<Id<T>>().GenerateId();
      }
      public class IndividualId : Id<Individual>
      {
          protected override Id<Individual> GenerateId()
          {
              var instance = new IndividualId();
              instance.Value = $"IndividualId-{Guid.NewGuid()}"; //Example of custom logic
              return instance;
          }
      }
      public class Transaction
      {
          private IId<IUser> _sender;
          private IId<IUser> _receiver;
          public Transaction(IId<IUser> sender, IId<IUser> receiver) { }
      }
      

      对于您有自定义逻辑的每种类型,请确保为Id&lt;yourtype&gt; 注册类型实例,例如在容器中为Id&lt;Individual&gt; 注册IndividualId

      那么为了使用,你可以这样使用:

      new Transaction(Id<Individual>.CreateNew(), Id<Organization>.CreateNew());
      

      【讨论】:

      • 我试图分享我对这个问题的看法。你对细节需求比较了解,但如果你知道如何解决问题,处理细节就不难了。
      • 这正是我的想法。唯一的问题是其他类(未实现 IUser)也需要一个 Id。因此,例如,我如何将您的代码与Id&lt;Transaction&gt;.CreateNew() 结合起来?
      • 在这种情况下,where 应该是 where T : IEntity 。并且任何实体都应该派生自IEntity
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-06-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多