在C#中,使用相同布局的不同命名空间中的类型强制转换。

13
我已开始编写 FedEx 的 Web 服务 API 接口。他们有三个不同的 API 我感兴趣; Rate,Ship 和 Track。我正在使用 SvcUtil.exe 生成服务代理。
不同的服务端点由 FedEx 在其自己的 WSDL 文件中各自指定。每个服务端点都有自己的 XML 命名空间(例如 http://fedex.com/ws/rate/v5http://fedex.com/ws/ship/v5)。
服务端点确实使用了许多相同的类型,例如 Address、Measurements、Weight、AuthenticationDetail、ClientDetail 等等...
问题就在这里,我可以同时提供所有 WSDL 文件给 SvcUtil.exe,并且通常它会将任何相同的类型合并为一个共享类型,但由于 FedEx 的每个服务都在自己的命名空间中,而且它们在该命名空间下的每个 WSDL 文件中重新声明这些类型,所以我最终得到的是一个地址(Address)、一个地址1(Address1)和一个地址2(Address2),每个命名空间都有一个。
为了解决这个问题,现在我会单独运行每个 WSDL 并将它们放在自己的 .NET 命名空间中 (例如 FedEx.Rate、FedEx.Ship、FedEx.Track)。问题是现在每个命名空间都有一个不同的地址类型(Fedex.Rate.Address,FedEx.Ship.Address)。
这使得在服务之间使用代码变得困难,例如 GetAuthenticationDetail() 工厂方法,这样我就不必在使用不同服务的每个地方重复该代码。

在C#中有没有方法将FedEx.Rate.Address强制转换为FedEx.Ship.Address?


+1 对于这个问题。我也遇到了同样的困难,从未找到一个好的解决方案。 - Darin Dimitrov
好问题;我原以为导入WSDL的方式有误,直到我仔细一看,他们确实给相同类型分配了不同的命名空间。 - Nicholas Piasecki
4个回答

9
如果类型相同,并且您可以控制源类,则可以在类中定义转换运算符,任何接受Rate.Address的函数也会自动接受Ship.Address。例如:
namespace Rate {
    class Address {
        string Street;
        string City;
        // ...

        public static implicit operator Ship.Address(Rate.Address addr) {
            Ship.Address ret;
            ret.Street = addr.Street;
            ret.City = addr.City;
            // ...

            return ret;
        }
    }
}

我的 C# 有点生疏,但我希望你能理解我的意思。


这种方法存在问题。例如,如果WSDL更改(添加了某个属性),并且使用svcutil.exe重新生成代理,则不必忘记更新隐式操作符方法,否则在运行时可能会出现一些奇怪的行为。 - Darin Dimitrov
嗯,我喜欢这种方法,而且几周前我还阅读了关于C#中的转换运算符的内容。让我试试看。FedEx的网络服务的一个好处就是,当他们发布新版本时,旧版本仍然可以无限期地使用。所以darin的评论不会给我造成太多问题。 - joshperry
也许我会考虑使用反射来实现运算符的主体部分,这样如果 WSDL 发生变化,只要您正确地设置了转换运算符,它就应该自动工作。 - joshperry
@joshperry,这似乎是对于那些可以容忍反射开销的项目的一个好解决方案。 - Darin Dimitrov
我接受了你的答案,因为它给了我解决问题所需的一切!请检查我的回答,其中包含我在隐式转换运算符中使用的代码。它使用反射来复制对象。我将实现一些显式转换以比较性能。 - joshperry
仅从风格的角度考虑,我建议将其作为显式运算符。这样,下一个阅读您代码的开发人员就不会被误导认为这两种类型是相同的。 - Christian Hayter

7

以下是我使用反射实现隐式转换运算符的方法。SvcUtil创建了部分类,因此我为每个转换方向添加了一个隐式转换运算符,因此在客户端代码中,您只需键入Type1 = Type2

在此片段中,WebAuthenticationCredentials是WebAuthenticationDetails的属性,因此在迭代源对象的属性时,如果类型不同(内置类型),则检查类型的名称(不包括命名空间),并递归调用具有这些属性的复制函数。

internal class ReflectionCopy
{
    public static ToType Copy<ToType>(object from) where ToType : new()
    {
        return (ToType)Copy(typeof(ToType), from);
    }

    public static object Copy(Type totype, object from)
    {
        object to = Activator.CreateInstance(totype);

        PropertyInfo[] tpis = totype.GetProperties(BindingFlags.Public | BindingFlags.Instance);
        PropertyInfo[] fpis = from.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

        // Go through each property on the "to" object
        Array.ForEach(tpis, tpi =>
        {
            // Find a matching property by name on the "from" object
            PropertyInfo fpi = Array.Find(fpis, pi => pi.Name == tpi.Name);
            if (fpi != null)
            {
                // Do the source and destination have identical types (built-ins)?
                if (fpi.PropertyType == tpi.PropertyType)
                {
                    // Transfer the value
                    tpi.SetValue(to, fpi.GetValue(from, null), null);
                }
                else
                {
                    // If type names are the same (ignoring namespace) copy them recursively
                    if (fpi.PropertyType.Name == tpi.PropertyType.Name)
                        tpi.SetValue(to, Copy(fpi.PropertyType, tpi.GetValue(from, null)), null);
                }
            }
        });

        return to;
    }
}

namespace Rate
{
    partial class WebAuthenticationDetail
    {
        public static implicit operator Ship.WebAuthenticationDetail(WebAuthenticationDetail from)
        {
            return ReflectionCopy.Copy<Ship.WebAuthenticationDetail>(from);
        }
    }

    partial class WebAuthenticationCredential
    {
        public static implicit operator Ship.WebAuthenticationCredential(WebAuthenticationCredential from)
        {
            return ReflectionCopy.Copy<Ship.WebAuthenticationCredential>(from);
        }
    }
}

namespace Ship
{
    partial class WebAuthenticationDetail
    {
        public static implicit operator Rate.WebAuthenticationDetail(WebAuthenticationDetail from)
        {
            return ReflectionCopy.Copy<Rate.WebAuthenticationDetail>(from);
        }
    }

    partial class WebAuthenticationCredential
    {
        public static implicit operator Rate.WebAuthenticationCredential(WebAuthenticationCredential from)
        {
            return ReflectionCopy.Copy<Rate.WebAuthenticationCredential>(from);
        }
    }
}

1

你可以通过创建自己的 Address 实现或使用其中一个稳定类型作为属性来使用运算符重载

一个例子:下面的 Address1 和 Address2 将分别成为你的 Rate.Address 和 Ship.Address

class Address1
{
    public string name = "Address1";
}
class Address2
{
    public string name = "Address2";
}

class GenericAddress
{
    public string name = "GenericAddress";
    public static implicit operator GenericAddress(Address1 a)
    {
        GenericAddress p = new GenericAddress(); p.name = a.name; return p;
    }
    public static implicit operator GenericAddress(Address2 a)
    {
        GenericAddress p = new GenericAddress(); p.name = a.name; return p;
    }
}
class Program
{
    static void Main(string[] args)
    {
        PrintName(new Address1());//prints address1
        PrintName(new Address2());//prints address2
    }

    static void PrintName(GenericAddress a)
    {
        Console.WriteLine(a.name);
    }
}

编辑:方法与上面的帖子相同,实现在一个单独的类中。


这个想法并不错,除了要复制所有已经存在的类型,这是很多工作量的。我曾考虑过编写一个代码生成器来完成类似的工作,但在团队环境中实现自定义的Visual Studio代码生成器是很困难的。 - joshperry

1

这些生成的类是否被定义为“partial”?如果是,您可以在不同的文件中扩展它们并提取一个接口,让所有地址类都实现它。


这样可以让您在代码中统一处理类型,但这不会让您将FedEx.Rate.Address传递给FedEx.Ship服务。 - Amy B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接