将接口实例化为TYPE并承诺一个类将遵循它(没有实际实现类)

时间:2018-01-10 11:25:32

标签: c# dictionary interface functional-programming

请原谅我没有提出一个好的问题标题。我不能在一个问题标题中适应这个问题。所以我有一个应用程序,我将玩家转换为类似于JSON格式的数据结构:

"players" = {

    "EU" = {

        "TestServer" = {

            "Slacker" = {

            },
            .
            . //more players
        },
        .
        . //more servers
    },
    .
    . //more regions
}

每个玩家都有Region,Server和其他一些数据,例如Scores。

public class PlayerModel {

    public string Name { get; set; }

    public RegionModel Region { get; set; } // just has string Name property
    public ServerModel Server { get; set; } // just has string Name property

    public ScoreModel[] Scores { get; set; }
}

在我的应用程序中,我有一些用于收集玩家数据的资源以及一种将其插入的方法:

private Dictionary<string /*region*/,
        Dictionary<string /*server*/,
        Dictionary<string /*playerName*/, PlayerModel>>> db;
这个荒谬的字典。我写了一个有效的插入方法(包含在问题末尾的代码),但它看起来很丑陋,我说服自己再次编写插入方法,它就像这样:

private void insertPlayer(PlayerModel player) {

    var node = db;

    var keys = (new [] {

        player.Region.Name,
        player.Server.Name
    }); 

    var new_node_found = keys.Aggregate(false, (uninit_found, key) => {

        if (uninit_found)
            node[key] = new Dictionary<string, T>(); // how would I ignore T

        else {
            if (!node.ContainsKey(key)) {

                uninit_found = true;
                node[key] = new Dictionary<string, T>();
            }
        }

        node = node[key] // conflicting types

        return uninit_found;
    });

    // should have reached the final node at this point
    // leaving the node having equal db[region][key = player.Server.Name]
    var key = player.Name;

    if (new_node_found || !node.ContainsKey(key)) {
        node[key] = player;

    } else {

        // player already exists at that node, so continue with
        // merging the player data with required business logic
    }
}

正如您所看到的那样,问题就出现了,

  1. 应该是什么?
  2. 我如何处理冲突的类型?我不是在处理JavaScript ......
  3. 所以我自学了,我真的不感兴趣:

    1. 字典值的类型,也不是键的类型
    2. 甚至不是字典本身
    3. 我只对字典的一些属性感兴趣,它可以简化为:

      public interface IPromise<TKey> {
      
          public bool ContainsKey(TKey key);
      
          public object /*WHATEVER*/ this[TKey key];
      }
      

      我觉得我可以继承NET的词典&lt;&gt;,并实现这个界面,也许会起作用,但仍然觉得这可以用其他更智能的方式完成。不幸的是,我没有标签和单词在搜索引擎上搜索这个。不知道如何克服“WHATEVER”类型和事情。如果我可以写成:

      private void insertPlayer(PlayerModel player) {
      
          // If i could just...
          var IPromised = interface<TKey> {
              public bool ContainsKey(TKey key);
              public object this[TKey key];
          } 
      
          // then...
          IPromised<string> node = db;
      
          // I promise you C# compiler, the db has the methods I defined in my interface
      
          var keys = (new [] {
      
              player.Region.Name,
              player.Server.Name
          }); 
      
          var new_node_found = keys.Aggregate(false, (uninit_found, key) => {
      
              if (uninit_found || !node.ContainsKey(key) /* meanwhile little opt- has found*/) {
      
                  uninit_found = true;
                  // I still promise you in the future, the dictionary's value still
                  // be providing the ContainsKey() and array indexor methods.
                  node[key] = new Dictionary<string, IPromised<string>>();
      
              }
      
              node = node[key]
      
              return uninit_found;
          });
      

      注意:我在编写此问题时创建了该方法,我无法保证代码可以按预期工作。但我认为它清楚地表明了我所遇到的问题。谢谢你的时间!

      附录:

      insertPlayer的丑陋工作版

      private void insertPlayer(PlayerModel player) {
      
          if (!db.ContainsKey(player.Region.Name))
              db[player.Region.Name] = new Dictionary<string, Dictionary<string, PlayerModel>>();
      
          if (!db[player.Region.Name].ContainsKey(player.Server.Name))
              db[player.Region.Name][player.Server.Name] = new Dictionary<string, PlayerModel>();
      
          if (!db[player.Region.Name][player.Server.Name].ContainsKey(player.Name))
              db[player.Region.Name][player.Server.Name][player.Name] = player;
      
          else {
      
              // merge player
              var playerRef = db[player.Region.Name][player.Server.Name][player.Name];
      
              playerRef.Scores = playerRef.Scores.Concat(player.Scores).Distinct(new PropertyComparer<ScoreModel>("Level")).ToArray();
          }
      }
      

      新插入方法概述:

      /*  At higest level, human language defination of the function:
      
          I will provide you list of keys, in an order of which follows like:
          grandparent, parent, child, grandchild...
      
          if the key is uninitialized at some level, initialize it and continue until
          you hit the final key. you don't need to check after this level 
          because the node is just initialized and rest will have to be initialized too.
      
          if not, and if you find a value is assigned at final key, execute the method
          I've provide to you and your job is done.
      */
      

      更新1

      结束了这个:(按照SOF上这个问题的答案:Cast to not explicitly implemented interface?

      private DictKeyEquals<string> db; // modified
      
      ... // somewhere in Form initialization:
      
      db = new Dictionary<string, object>().GetPromise();
      

      扩展:

      public interface DictKeyEquals<TKey> {
      
          bool ContainsKey(TKey key);
          object this[TKey key] { get; set; }
      }
      
      public static class DictionaryExtensions {
      
          private sealed class DictionaryWrapper<TKey, TValue> : DictKeyEquals<TKey> {
      
              private readonly Dictionary<TKey, TValue> dict;
      
              public DictionaryWrapper(Dictionary<TKey, TValue> dict) {
                  this.dict = dict;
              }
      
              public object this[TKey key] {
                  get => dict[key];
                  set => dict[key] = (TValue)value;
              }
      
              public bool ContainsKey(TKey key) {
                  return dict.ContainsKey(key);
              }
          }
      
          public static DictKeyEquals<TKey> GetPromise<TKey, TValue>(this Dictionary<TKey, TValue> dict) {
              return new DictionaryWrapper<TKey, TValue>(dict);
          }
      }
      

      插入方法(为了执行单元测试而公开):

      public void InsertPlayer(PlayerModel player) {
      
          var node = db;
      
          var keys = (new[] {
      
              player.Region.Name,
              player.Realm.Name
          });
      
          var new_node_found = keys.Aggregate(false, (uninit_found, key) => {
      
              if (uninit_found || !node.ContainsKey(key)) {
      
                  uninit_found = true;
                  node[key] = new Dictionary<string, object>().GetPromise(); // Line A
              }
      
              node = (DictKeyEquals<string>)node[key]; // Line B
      
              return uninit_found;
          });
      
          // should have reached the final node at this point
          // leaving the node having equal db[key = player.Realm.Name]
          //var key = player.Name;
      
          var finalKey = player.Name;
      
          if (new_node_found || !node.ContainsKey(finalKey)) {
              node[finalKey] = player;
      
          } else {
      
              // player already exists at that node, so continue with
              // merging the player data with required business logic
          }
      }
      

      更新2

      我发现我不需要扩展和接口,只要我坚持:

      private Dictionary<string, object> db;
      

      在插入方法中进行了少量修改:

      ...
      node[key] = new Dictionary<string, object>(); // Line A
      ...
      node = (Dictionary<string, object>)node[key]; // Line B
      ...
      

      我仍然将这个问题视为开放式,我会为可能更好的设计模式和功能打开赏金。

      更新2备注:我觉得我想为人们留下一些意见。出现问题是因为我很顽固:

      private Dictionary<string /*region*/,
              Dictionary<string /*server*/,
              Dictionary<string /*playerName*/, PlayerModel>>> db;
      

      词典本身从一开始。让我思考错误模式的是我教过我知道JSON树的最大深度,并从定义这样的字典开始限制树。与此同时,新的Insert_player方法将会没有限制来遍历字典。但是我已经通过使用字典限制了这一点。在更新1 中,已经开始使用扩展进行修改,要求db为某些字典,例如具有typeof对象的值。因此,更新2 我摆脱了所有扩展和接口,提出了更容易阅读和理解的内容。

2 个答案:

答案 0 :(得分:1)

不确定你想要做什么,但我想你应该做什么:

  1. 将一些来自外部源的数据加载到某种内存数据库

  2. 能够修改数据

  3. 查询一些统计信息

  4. 这是我的简化建议:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace ConsoleApp1
    {
        class Program
        {
            public class Region
            {
                public string Name;
    
                // additional data ...
            }
    
            public class Server
            {
                public string Name;
    
                public string RegionName;
    
                // additional data ...
            }
    
            public class Player
            {
                public string Name;
    
                public int Scores;
    
                public string ServerName;
            }
    
            public class Database
            {
                public List<Region> Regions;
    
                public List<Server> Servers;
    
                public List<Player> Players;
            }
    
            static void Main(string[] args)
            {
                var db = new Database
                {
                    Regions = new List<Region>
                    {
                        new Region {Name = "EU"},
                        new Region { Name = "US"}
                    },
                    Servers = new List<Server>
                    {
                        new Server { Name = "EuServer1", RegionName = "EU"},
                        new Server { Name = "EuServer2", RegionName = "EU"},
                        new Server { Name = "UsServer1", RegionName = "US"},
                        new Server { Name = "UsServer2", RegionName = "US"}
                    },
                    Players = new List<Player>
                    {
                        new Player {Name = "EuName1", ServerName = "EuServer1", Scores = 1},
                        new Player {Name = "EuName2", ServerName = "EuServer2", Scores = 2},
                        new Player {Name = "UsName1", ServerName = "UsServer1", Scores = 3},
                        new Player {Name = "UsName2", ServerName = "UsServer2", Scores = 4},
                        new Player {Name = "UsName3", ServerName = "UsServer2", Scores = 5}
                    }
                };
    
                // inserting new player
                db.Players.Add(new Player { Name = "newPlayer1", ServerName = "UsName2", Scores = 7 });
    
                // total scores by region
                foreach(var region in db.Regions)
                {
                    var totalScore = from player in db.Players
                                     join server in db.Servers on player.ServerName equals server.Name
                                     where server.RegionName == region.Name
                                     select player.Scores;
                    Console.WriteLine($"Region: {region.Name}, Total Score: {totalScore.Sum()}");
                }
    
                // you can query whatever you want using LINQ...
            }
        }
    }
    

    Database类的未来改进:

    1. 隐藏RegionsServersPlayers字段,并添加AddPlayerAddRegionRemovePlayerCRUD种方法等等,它将处理数据整合检查(即在将新播放器添加到列表之前检查区域和服务器是否存在,删除在某些服务器上播放的所有播放器,然后服务器将从服务器列表中删除等等)

    2. 如果遇到性能问题,请使用词典加快搜索速度

    3. 使用Entity Framework Core with In-Memory Databasde提供商

答案 1 :(得分:1)

您需要Type安全和动态泛型。你也不可能吃馅饼。

此外,您打算编写的代码对于这样小的专长来说也是不必要的。

你的丑陋&#34;代码更准确,但考虑到所有哈希表查找,它在性能上并不好。

我相信,遵循代码更具可读性,更少&#34;丑陋&#34;并且看起来很少有工作。

private static void InsertPlayer(PlayerModel player)
{
    var server = db.EnsureGet(player.Region).EnsureGet(player.Server);

    PlayerModel existingPlayer;
    if (server.TryGetValue(player.Name, out existingPlayer))
    {
        //TODO: merge
    }
    else
        server[player.Name] = player;
}

public static T EnsureGet<T>(this Dictionary<string, T> dictionary, string key) where T : new()
{
    T item;
    if (dictionary.TryGetValue(key, out item))
        return item;

    dictionary.Add(key, item = new T());

    return item;
}