【问题标题】:How to make method reusable with different types如何使不同类型的方法可重用
【发布时间】:2016-06-23 14:43:06
【问题描述】:

我在 Visual Studio 2015 中使用 MVVM Light 来构建 WPF 应用程序。代码中有一种方法在代码中重复了 4 次,但略有变化;唯一的区别是被修改的ObservableCollection的类型和数据服务层调用的方法。

这是方法,它返回一个StatusViewModel对象的ObservableCollection,用于填充ComboBoxStatusVm 用于绑定到ComboBoxSelectedItem,设置为集合中的第一项并且为“空白”:

private async Task<ObservableCollection<StatusViewModel>> GetStatuses()
{
    var result = new ObservableCollection<StatusViewModel>();
    var blank = new StatusViewModel
    {
        StatusId = -1,
        Status = null,
        Description = null,
        IsActive = false,
        CreatedDate = DateTime.Now
    };
    result.Add(blank);
    var dataService = new MyDataService();
    foreach (var c in await dataService.GetStatuses())
        result.Add(c);
    StatusVm =
        result.SingleOrDefault(c => c.StatusId.Equals(-1));
    return result;
}

这是StatusVm 的私有字段和公共属性:

private StatusViewModel _statusVm;
public StatusViewModel StatusVm
{
    get { return _statusVm; }
    set
    {
        if (Equals(value, _statusVm)) return;
        _statusVm = value;
        RaisePropertyChanged();
    }
}   

现在想象上面的内容再重复 3 次,还有 3 种 VM 类型!如何使GetStatuses() 成为可以采用不同视图模型类型并在数据服务上调用适当方法的方法?谢谢。

更新:下面是另一种类型的属性和方法:

private MroViewModel_mroVm;
public MroViewModel MroVm
{
    get { return _mroVm; }
    set
    {
        if (Equals(value, _mroVm)) return;
        _mroVm = value;
        RaisePropertyChanged();
    }
}   

private async Task<ObservableCollection<MroViewModel>> GetMro()
{
    var result = new ObservableCollection<MroViewModel>();
    var blank = new MroViewModel
    {
        StatusId = -1,
        Status = null,
        Description = null,
        IsActive = false,
        CreatedDate = DateTime.Now
    };
    result.Add(blank);
    var dataService = new MyDataService();
    foreach (var c in await dataService.GetMro())
        result.Add(c);
    MroVm =
        result.SingleOrDefault(c => c.StatusId.Equals(-1));
    return result;
}

【问题讨论】:

  • 泛化GetStatuses()的两个问题是blank的初始化和对web方法GetStatuses()的调用。前者我会通过要求视图模型适当地初始化来处理,或者如果这不切实际,则通过要求它们实现一个返回 blank 实例的接口来处理。然后可能要求调用者传入一个调用 web 方法的 lambda,或者要求视图模型提供一个 CallWebMethod() 方法。或者将其添加到我上面提到的界面中。称它为IWebMethodRetrievable 或类似名称。界面的想法在我心中成长。
  • 如果所有类型都具有相同的属性,那么通用方法 lile private async Task> GetStatuses(T t) 怎么样?
  • 你能发布其他方法吗?
  • @Alex 实际上 lambda 也需要异步,不是吗。这越来越丑了。让我摆弄一下。
  • @Alex 你知道吗,我也错过了StatusVm 初始化部分。一旦你排除了所有不能通用的东西,剩下的就不多了。我认为这实际上是 copy'n'paste(或 sn-p)是最不难看的选择的情况之一。

标签: c# wpf mvvm


【解决方案1】:

您将为常用属性创建接口。

internal interface IStatusViewModel {
        int StatusId { get; set; }
        string Status { get; set; }
        string Description { get; set; }
        bool IsActive { get; set; }
        DateTime CreatedDate { get; set; }
}

在需要检索状态的类中实现接口

internal class MroViewModel : IStatusViewModel {
        public int StatusId { get; set; }
        public string Status { get; set; }
        public string Description { get; set; }
        public bool IsActive { get; set; }
        public DateTime CreatedDate { get; set; }
}

将方法设为静态并传递服务函数,该函数将调用适当的方法来检索旧状态。

public static async Task<ObservableCollection<T>> GetStatuses<T>(
    Func<MyDataService, Task<IEnumerable<T>>> retrieveStatusesAction)
    where T : IStatusViewModel, new()
{
    var result = new ObservableCollection<T>();
    var blank = new T
    {
        StatusId = -1,
        Status = null,
        Description = null,
        IsActive = false,
        CreatedDate = DateTime.Now
    };
    result.Add(blank);

    var dataService = new MyDataService();
    foreach (var c in await retrieveStatusesAction(dataService))
        result.Add(c);

    // TODO Implement Expression<Func<TSource, TResult>> projection for assigning to VM
    StatusVm = result.SingleOrDefault(c => c.StatusId.Equals(-1));

    return result;
}

然后你可以这样调用这个方法:

GetStatuses((service) => service.GetMro());

我没有对此进行测试,需要使用表达式编译来分配 StatusVm。我现在将看看如何做到这一点,但想法就在那里。

对于表达式和属性分配: Property selector Expression<Func<T>>. How to get/set value to selected property

-- 编辑--

VM 分配是这样的:

    public static async Task<ObservableCollection<T>> GetStatuses<T, TContainer>(
        TContainer instance,
        Expression<Func<TContainer, T>> viewModelProjection,
        Func<MyDataService, Task<IEnumerable<T>>> retrieveStatusesAction)
        where T : IStatusViewModel, new()
    {
        var result = new ObservableCollection<T>();
        var blank = new T
        {
            StatusId = -1,
            Status = null,
            Description = null,
            IsActive = false,
            CreatedDate = DateTime.Now
        };
        result.Add(blank);

        var dataService = new MyDataService();
        foreach (var c in await retrieveStatusesAction(dataService))
            result.Add(c);

        var vmStatus = result.SingleOrDefault(c => c.StatusId.Equals(-1));

        // Warning: Check casted values, this is unsafe
        var vm = (PropertyInfo)((MemberExpression)viewModelProjection.Body).Member;
        vm.SetValue(instance, vmStatus, null);

        return result;
    }

然后你会像这样调用方法:

await GetStatuses(this, inst => inst.MroVm, (service) => service.GetMro());

如果你不熟悉表达式,我会解释。 第一个参数是视图模型实例所在的对象。第二个参数从该对象中选择与需要更改的视图模型相对应的属性。最后一个参数是函数,它接受服务并返回检索状态的适当方法 - 这就像 C++ 中指向函数的指针。

这将编译,但不确定它是否会按预期运行。它应该。如果有问题,请写在cmets中。

【讨论】:

    【解决方案2】:

    您可以定义接口并尝试 StrategyFactory 的组合,如下所示(为简单起见,我跳过了 async/await):

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Starting");
            var mainVm = new MainViewModel();
            mainVm.GetStatuses();
            mainVm.GetMro();
    
            Console.WriteLine("Status: {0} {1}", mainVm.StatusVm.Name, mainVm.StatusVm.CreateDate);
            Console.WriteLine("MroVm: {0} {1}", mainVm.MroVm.Name, mainVm.MroVm.CreateDate);
        }
    }
    
    public class MainViewModel
    {
        public StatusViewModel StatusVm { get; set; }
        public MroViewModel MroVm { get; set; }
    
        public void GetStatuses()
        {
            var result =  Get(VmKind.Status);
            StatusVm = result.SingleOrDefault(c => c.StatusId.Equals(-1)) as StatusViewModel;
        }
        public void GetMro()
        {
            var result = Get(VmKind.Mro);
            MroVm = result.SingleOrDefault(c => c.StatusId.Equals(-1)) as MroViewModel;
        }
    
        public IEnumerable<IVm> Get(VmKind vmKind)
        {
            var dataService = new MyDataService();
            return dataService.Get(vmKind);
        }
    
    
    }
    
    public interface IVm
    {
        int StatusId { get; set; }
        DateTime CreateDate { get; set; }
        string Name { get; }
    }
    
    public class StatusViewModel : IVm
    {
        public DateTime CreateDate { get; set; }
        public int StatusId { get; set; }
        public string Name { get { return "StatusViewModel"; } }
    }
    
    public class MroViewModel : IVm
    {
        public DateTime CreateDate { get; set; }
        public int StatusId { get; set; }
        public string Name { get { return "MroViewModel"; } }
    }
    
    public enum VmKind    {Status, Mro }
    
    #region Strategy
    
    public interface IDataGetter
    {
        IEnumerable<IVm> Get(VmKind vmKind);
    }
    
    public class MyDataService : IDataGetter {
        public IEnumerable<IVm> Get(VmKind vmKind)
        {
            switch (vmKind)
            {
                case VmKind.Status:
                    return GetStatuses();
                    //break;
                case VmKind.Mro:
                    return GetMro();
                    //break;
                default:
                    throw new ArgumentException("Unknown VM type");
            }
        }
    
        private IEnumerable<IVm> GetMro()
        {
            return new List<MroViewModel> {
                new MroViewModel { StatusId = -1, CreateDate = DateTime.Now },
                new MroViewModel { StatusId = 2, CreateDate = DateTime.Now }
            };
        }
    
        private IEnumerable<StatusViewModel> GetStatuses()
        {
            return new List<StatusViewModel> {
                new StatusViewModel { StatusId = -1, CreateDate = DateTime.Now },
                new StatusViewModel { StatusId = 2, CreateDate = DateTime.Now }
            };
        }
    }
    #endregion
    
    #region Factory
    public class VmFactory {
        static IVm Create(VmKind vmKind)
        {
            IVm result = null;
            switch (vmKind)
            {
                case VmKind.Status:
                    result = new StatusViewModel { StatusId = -1, CreateDate = DateTime.Now };
                    break;
                case VmKind.Mro:
                    result = new MroViewModel { StatusId = -1, CreateDate = DateTime.Now };
                    break;
                default:
                    throw new ArgumentException("Unknown VM type");
                    //break;
            }
            return result;
        }
    }
    #endregion
    

    我实际上并没有在这里使用 Factory,但您可以使用它来轻松创建 VM。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-23
      • 1970-01-01
      • 2011-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多