使用时不一致的行为与动态类型等待

时间:2021-12-16 15:31:56

I'm trying to use dynamic to go around inconveniences caused by design or lack of it (the "inconvenience" can be found here, if interested Simplify method retrieving data from generic repository).

我正在尝试使用dynamic来解决由于设计或缺少它而带来的不便(如果有兴趣简化从通用存储库检索数据的方法,可以在这里找到“不便”)。

To make it short, I need to return collection of Entity instances. Class is pretty simple:

为了简短,我需要返回实体实例的集合。类非常简单:

[JsonObject]
public class Entity
{
    [PrimaryKey]
    [JsonProperty(PropertyName = "id")]
    public virtual int Id { get; set; }

    [JsonIgnore]
    public string Content { get; set; }
}

So Entity has only Id and Content. Inheriting classes might have other properties but I'm only interested about the Content part (complex JSON).

所以实体只有Id和内容。继承类可能有其他属性,但我只对内容部分(复杂的JSON)感兴趣。

All kinds of different entities can be accessed via generic Repository<T>. I need to know the Type of concrete class because T maps to underlying SQLite tables via data provider, built on top of SQLite-net ORM.

可以通过通用存储库 访问各种不同的实体。我需要知道具体类的类型,因为T通过数据提供者映射到底层的SQLite表,它构建在SQLite-net ORM之上。

So, for example, if I have Schedule : Entity, then I'd be using Repository<Schedule> to manipulate table named Schedule. This part works just fine.

例如,如果我有Schedule: Entity,那么我将使用Repository 来操作名为Schedule的表。这部分还可以。

// must be instantiated with concrete class/type inheriting
// from Entity in order to map to correct database table
public class Repository<T> where T : new()
{
    public async virtual Task<IEnumerable<T>> GetAllAsync()
    {
        return await SQLiteDataProvider.Connection.Table<T>().ToListAsync();
    }
    // etc.
}

Main problem is that "commands" are coming from JavaScript client so I'll receive requests in JSON format. In this JSON I have a property called CollectionName which specifies the desired table (and the concrete type).

主要的问题是“命令”来自JavaScript客户机,因此我将接收JSON格式的请求。在这个JSON中,我有一个名为CollectionName的属性,它指定所需的表(以及具体的类型)。

What I need/want is a nice & clean piece of code that can fetch entities from any given table. So, method below was supposed to solve all my problems, but turns out it didn't...

我需要/想要的是一段漂亮干净的代码,可以从任何给定的表中获取实体。下面的方法应该可以解决我所有的问题,但结果并不是……

public async Task<IEnumerable<Entity>> GetAllEntitiesFrom(CollectionArgs args)
{
    // args.CollectionName is type of entity as string
    // namespace + collection name is mapped as correct type
    // e.g. MyNamespace.Schedule
    Type entityType = Type.GetType(
        string.Format("{0}{1}", EntityNamespacePrefix, args.CollectionName), true, true);

    // get correct repository type using resolved entity type
    // e.g. Repository<MyNamespace.Schedule>
    Type repositoryType = typeof(Repository<>).MakeGenericType(entityType);
    dynamic repository = Activator.CreateInstance(repositoryType);

    // Below `GetAllAsync()` returns `Task<IEnumerable<T>>`.

    // this blocking call works 100%
    //var entities = repository.GetAllAsync().Result;

    // this non-blocking call works when it feels like it
    var entities = await repository.GetAllAsync();

    return entities;
}

So if (above) I use blocking .Result everything works liek a charm. Instead, if I use await, code might or might not work. It really seems to depend on positions of planets and/or mood swings of Flying Spaghetti Monster.

所以如果(上面的)我使用阻塞。结果一切工作都散发着魅力。相反,如果我使用wait,代码可能会工作,也可能不会工作。这似乎真的取决于行星的位置和/或飞行的意大利面怪物的情绪波动。

Randomly, but more often than not, given line will be throwing

随机的,但通常情况下,给定的线将会投掷。

Unable to cast object of type 'System.Runtime.CompilerServices.TaskAwaiter'1[System.Collections.Generic.IEnumerable'1[MyNamespace.Schedule]]' to type 'System.Runtime.CompilerServices.INotifyCompletion'.

无法强制类型为'System.Runtime.CompilerServices.TaskAwaiter'1[System.Collections.Generic.IEnumerable'1] [MyNamespace]。安排]]”输入“System.Runtime.CompilerServices.INotifyCompletion”。

I'm using .NET 4.0 Extended Framework.

我正在使用。net 4.0扩展框架。

2 个解决方案

#1


1  

It can be achieved with 2 dynamic calls:

它可以通过两个动态调用实现:

public async Task<IEnumerable<Entity>> GetAllEntitiesFrom(CollectionArgs args)
{
    var entityType = Type.GetType(
        string.Format("{0}{1}", EntityNamespacePrefix, args.CollectionName), true, true);
    var repositoryType = typeof(Repository<>).MakeGenericType(entityType);
    var repository = Activator.CreateInstance(repositoryType);
    var task = (Task)((dynamic)repository).GetAllAsync();
    await task;
    var entities = (IEnumerable<Entity>)((dynamic)task).Result;
    return entities;
}  

Edit
Although the above should work, there should be a better overall design. Unfortunately MS decided to use tasks for asynchrony, and since Task<TResult> is class, we cannot take benefit from covariance. However, we can do that with the help of a little generic extension with the cost of a little GC garbage. But IMO it greatly simplifies such designs/implementations. Check it out:

尽管上面的操作应该有效,但是应该有更好的整体设计。不幸的是,MS决定将任务用于异步,由于任务 是类,所以我们不能从协方差中获益。但是,我们可以通过一个小的通用扩展来实现这一点,而代价是一个小的GC垃圾。但是,IMO大大简化了这些设计/实现。检查一下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

namespace Tests
{
    // General async extensions
    public interface IAwaitable<out TResult>
    {
        IAwaiter<TResult> GetAwaiter();
        TResult Result { get; }
    }
    public interface IAwaiter<out TResult> : ICriticalNotifyCompletion, INotifyCompletion
    {
        bool IsCompleted { get; }
        TResult GetResult();
    }
    public static class AsyncExtensions
    {
        public static IAwaitable<TResult> AsAwaitable<TResult>(this Task<TResult> task) { return new TaskAwaitable<TResult>(task); }
        class TaskAwaitable<TResult> : IAwaitable<TResult>, IAwaiter<TResult>
        {
            TaskAwaiter<TResult> taskAwaiter;
            public TaskAwaitable(Task<TResult> task) { taskAwaiter = task.GetAwaiter(); }
            public IAwaiter<TResult> GetAwaiter() { return this; }
            public bool IsCompleted { get { return taskAwaiter.IsCompleted; } }
            public TResult Result { get { return taskAwaiter.GetResult(); } }
            public TResult GetResult() { return taskAwaiter.GetResult(); }
            public void OnCompleted(Action continuation) { taskAwaiter.OnCompleted(continuation); }
            public void UnsafeOnCompleted(Action continuation) { taskAwaiter.UnsafeOnCompleted(continuation); }
        }
    }
    // Your entity framework
    public abstract class Entity
    {
        // ...
    }
    public interface IRepository<out T>
    {
        IAwaitable<IEnumerable<T>> GetAllAsync();
    }
    public class Repository<T> : IRepository<T> where T : Entity
    {
        public IAwaitable<IEnumerable<T>> GetAllAsync() { return GetAllAsyncCore().AsAwaitable(); }
        protected async virtual Task<IEnumerable<T>> GetAllAsyncCore()
        {
            //return await SQLiteDataProvider.Connection.Table<T>().ToListAsync();

            // Test
            await Task.Delay(1000);
            return await Task.FromResult(Enumerable.Empty<T>());
        }
    }
    public static class Repository
    {
        public static IAwaitable<IEnumerable<Entity>> GetAllEntitiesFrom(string collectionName)
        {
            var entityType = Type.GetType(typeof(Entity).Namespace + "." + collectionName, true, true);
            var repositoryType = typeof(Repository<>).MakeGenericType(entityType);
            var repository = (IRepository<Entity>)Activator.CreateInstance(repositoryType);
            return repository.GetAllAsync();
        }
    }
    // Test
    class EntityA : Entity { }
    class EntityB : Entity { }
    class Program
    {
        static void Main(string[] args)
        {
            var t = Test();
            t.Wait();
        }
        static async Task Test()
        {
            var a = await Repository.GetAllEntitiesFrom(typeof(EntityA).Name);
            var b = await Repository.GetAllEntitiesFrom(typeof(EntityB).Name);
        }
    }
}

#2


3  

If the Repository<T> type is a type of your own making, you can have it be based on an abstract base type that has an abstract Task<IEnumerable<Entity>> GetAllAsync(). Then, since your Repository apparently already has a method of that signature - so you're good:

如果存储库 类型是您自己创建的类型,您可以让它基于具有抽象任务 > GetAllAsync()的抽象基类型。然后,由于您的存储库显然已经有了一个签名的方法——所以您很好:

public abstract class Repository
{
  public abstract Task<IEnumerable<Entity>> GetAllAsync();
}

Then have your Repository<Entity> be based on Repository.

然后使存储库 基于存储库。

public class Repository<T>: Repository where T: Entity  // Your existing class
{
  public override async Task<IEnumerable<Entity>> GetAllAsync()
  {
    //  Your existing implementation
  }
  //...existing stuff...
}

Then, when using it, instead of dynamic, you can say:

然后,当使用它时,你可以说:

public async Task<IEnumerable<Entity>> GetAllEntitiesFrom(CollectionArgs args)
{
  var entityType = 
    Type.GetType(
      string.Format(
        "{0}{1}", 
        EntityNamespacePrefix, 
        args.CollectionName), 
      true, 
      true);

  var repositoryType =
    typeof(Repository<>)
    .MakeGenericType(entityType);

  var repository = 
    (Repository) Activator
    .CreateInstance( repositoryType );

  return repository.GetAllAsync();  // await not required
}

No dynamics at all.

没有动力。

#1


1  

It can be achieved with 2 dynamic calls:

它可以通过两个动态调用实现:

public async Task<IEnumerable<Entity>> GetAllEntitiesFrom(CollectionArgs args)
{
    var entityType = Type.GetType(
        string.Format("{0}{1}", EntityNamespacePrefix, args.CollectionName), true, true);
    var repositoryType = typeof(Repository<>).MakeGenericType(entityType);
    var repository = Activator.CreateInstance(repositoryType);
    var task = (Task)((dynamic)repository).GetAllAsync();
    await task;
    var entities = (IEnumerable<Entity>)((dynamic)task).Result;
    return entities;
}  

Edit
Although the above should work, there should be a better overall design. Unfortunately MS decided to use tasks for asynchrony, and since Task<TResult> is class, we cannot take benefit from covariance. However, we can do that with the help of a little generic extension with the cost of a little GC garbage. But IMO it greatly simplifies such designs/implementations. Check it out:

尽管上面的操作应该有效,但是应该有更好的整体设计。不幸的是,MS决定将任务用于异步,由于任务 是类,所以我们不能从协方差中获益。但是,我们可以通过一个小的通用扩展来实现这一点,而代价是一个小的GC垃圾。但是,IMO大大简化了这些设计/实现。检查一下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

namespace Tests
{
    // General async extensions
    public interface IAwaitable<out TResult>
    {
        IAwaiter<TResult> GetAwaiter();
        TResult Result { get; }
    }
    public interface IAwaiter<out TResult> : ICriticalNotifyCompletion, INotifyCompletion
    {
        bool IsCompleted { get; }
        TResult GetResult();
    }
    public static class AsyncExtensions
    {
        public static IAwaitable<TResult> AsAwaitable<TResult>(this Task<TResult> task) { return new TaskAwaitable<TResult>(task); }
        class TaskAwaitable<TResult> : IAwaitable<TResult>, IAwaiter<TResult>
        {
            TaskAwaiter<TResult> taskAwaiter;
            public TaskAwaitable(Task<TResult> task) { taskAwaiter = task.GetAwaiter(); }
            public IAwaiter<TResult> GetAwaiter() { return this; }
            public bool IsCompleted { get { return taskAwaiter.IsCompleted; } }
            public TResult Result { get { return taskAwaiter.GetResult(); } }
            public TResult GetResult() { return taskAwaiter.GetResult(); }
            public void OnCompleted(Action continuation) { taskAwaiter.OnCompleted(continuation); }
            public void UnsafeOnCompleted(Action continuation) { taskAwaiter.UnsafeOnCompleted(continuation); }
        }
    }
    // Your entity framework
    public abstract class Entity
    {
        // ...
    }
    public interface IRepository<out T>
    {
        IAwaitable<IEnumerable<T>> GetAllAsync();
    }
    public class Repository<T> : IRepository<T> where T : Entity
    {
        public IAwaitable<IEnumerable<T>> GetAllAsync() { return GetAllAsyncCore().AsAwaitable(); }
        protected async virtual Task<IEnumerable<T>> GetAllAsyncCore()
        {
            //return await SQLiteDataProvider.Connection.Table<T>().ToListAsync();

            // Test
            await Task.Delay(1000);
            return await Task.FromResult(Enumerable.Empty<T>());
        }
    }
    public static class Repository
    {
        public static IAwaitable<IEnumerable<Entity>> GetAllEntitiesFrom(string collectionName)
        {
            var entityType = Type.GetType(typeof(Entity).Namespace + "." + collectionName, true, true);
            var repositoryType = typeof(Repository<>).MakeGenericType(entityType);
            var repository = (IRepository<Entity>)Activator.CreateInstance(repositoryType);
            return repository.GetAllAsync();
        }
    }
    // Test
    class EntityA : Entity { }
    class EntityB : Entity { }
    class Program
    {
        static void Main(string[] args)
        {
            var t = Test();
            t.Wait();
        }
        static async Task Test()
        {
            var a = await Repository.GetAllEntitiesFrom(typeof(EntityA).Name);
            var b = await Repository.GetAllEntitiesFrom(typeof(EntityB).Name);
        }
    }
}

#2


3  

If the Repository<T> type is a type of your own making, you can have it be based on an abstract base type that has an abstract Task<IEnumerable<Entity>> GetAllAsync(). Then, since your Repository apparently already has a method of that signature - so you're good:

如果存储库 类型是您自己创建的类型,您可以让它基于具有抽象任务 > GetAllAsync()的抽象基类型。然后,由于您的存储库显然已经有了一个签名的方法——所以您很好:

public abstract class Repository
{
  public abstract Task<IEnumerable<Entity>> GetAllAsync();
}

Then have your Repository<Entity> be based on Repository.

然后使存储库 基于存储库。

public class Repository<T>: Repository where T: Entity  // Your existing class
{
  public override async Task<IEnumerable<Entity>> GetAllAsync()
  {
    //  Your existing implementation
  }
  //...existing stuff...
}

Then, when using it, instead of dynamic, you can say:

然后,当使用它时,你可以说:

public async Task<IEnumerable<Entity>> GetAllEntitiesFrom(CollectionArgs args)
{
  var entityType = 
    Type.GetType(
      string.Format(
        "{0}{1}", 
        EntityNamespacePrefix, 
        args.CollectionName), 
      true, 
      true);

  var repositoryType =
    typeof(Repository<>)
    .MakeGenericType(entityType);

  var repository = 
    (Repository) Activator
    .CreateInstance( repositoryType );

  return repository.GetAllAsync();  // await not required
}

No dynamics at all.

没有动力。