岁月蹉跎敢问人生几何 發表於 2025-8-27 00:06:00

婶可忍叔不可忍的AutoMapper,你还用吗?

<h2 id="automapper是让人又爱又恨的项目">AutoMapper是让人又爱又恨的项目</h2>
<blockquote>
<ul>
<li>爱它是因为它解决了一些问题,很多项目都有用,下载量很大,受众很广。</li>
<li>恨它是因为它诸多反人类的设计。</li>
<li>为此本人开源项目PocoEmit对标AutoMapper。</li>
</ul>
</blockquote>
<h2 id="1-automapper反人类设计">1. AutoMapper反人类设计</h2>
<h3 id="11-automapper注册代码">1.1 AutoMapper注册代码</h3>
<pre><code class="language-csharp">services.AddAutoMapper(cfg =&gt; cfg.CreateMap&lt;User, UserDTO&gt;());
</code></pre>
<p>User和UserDTO除了类名不一样,其他都一样,怎么看这行代码都多余。<br>
需要转化的类型越多,多余的代码就越多。<br>
类型转化不应该就是个静态方法吗?而且AutoMapper注册却依赖容器,Mapper对象也是从容器获取。<br>
本人觉得AutoMapper设计的太反人类了。</p>
<h3 id="12-pocoemit对于大部分转化是不需要手动配置">1.2 PocoEmit对于大部分转化是不需要手动配置</h3>
<blockquote>
<ul>
<li>PocoEmit可以轻松的定义静态实例。</li>
<li>PocoEmit静态实例可以用来定义静态委托字段,当静态方法用。</li>
</ul>
</blockquote>
<pre><code class="language-csharp">UserDTO dto = PocoEmit.Mapper.Default.Convert&lt;User, UserDTO&gt;(new User());
</code></pre>
<pre><code class="language-csharp">public static readonly Func&lt;User, UserDTO&gt; UserDTOConvert = PocoEmit.Mapper.Default.GetConvertFunc&lt;User, UserDTO&gt;();
</code></pre>
<h2 id="2-automapper的性能差强人意">2. AutoMapper的性能差强人意</h2>
<h3 id="21-以下是automapper官网例子与pocoemitmapper的对比">2.1 以下是AutoMapper官网例子与PocoEmit.Mapper的对比</h3>
<blockquote>
<ul>
<li>Customer转化为CustomerDTO(嵌套多个子对象、数组及列表)。</li>
<li>Auto是执行AutoMapper的IMapper.Map方法。</li>
<li>Poco是执行PocoEmit.Mapper的IMapper.Convert方法。</li>
<li>PocoFunc是执行PocoEmit.Mapper生成的委托。</li>
</ul>
</blockquote>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right">Mean</th>
<th style="text-align: right">Error</th>
<th style="text-align: right">StdDev</th>
<th style="text-align: right">Median</th>
<th style="text-align: right">Ratio</th>
<th style="text-align: right">RatioSD</th>
<th style="text-align: right">Gen0</th>
<th style="text-align: right">Allocated</th>
<th style="text-align: right">Alloc Ratio</th>
</tr>
</thead>
<tbody>
<tr>
<td>Auto</td>
<td style="text-align: right">89.30 ns</td>
<td style="text-align: right">1.006 ns</td>
<td style="text-align: right">1.118 ns</td>
<td style="text-align: right">90.17 ns</td>
<td style="text-align: right">1.46</td>
<td style="text-align: right">0.03</td>
<td style="text-align: right">0.0260</td>
<td style="text-align: right">448 B</td>
<td style="text-align: right">1.08</td>
</tr>
<tr>
<td>Poco</td>
<td style="text-align: right">61.31 ns</td>
<td style="text-align: right">1.036 ns</td>
<td style="text-align: right">1.194 ns</td>
<td style="text-align: right">61.25 ns</td>
<td style="text-align: right">1.00</td>
<td style="text-align: right">0.03</td>
<td style="text-align: right">0.0241</td>
<td style="text-align: right">416 B</td>
<td style="text-align: right">1.00</td>
</tr>
<tr>
<td>PocoFunc</td>
<td style="text-align: right">42.56 ns</td>
<td style="text-align: right">0.066 ns</td>
<td style="text-align: right">0.073 ns</td>
<td style="text-align: right">42.56 ns</td>
<td style="text-align: right">0.69</td>
<td style="text-align: right">0.01</td>
<td style="text-align: right">0.0223</td>
<td style="text-align: right">384 B</td>
<td style="text-align: right">0.92</td>
</tr>
</tbody>
</table>
<blockquote>
<ul>
<li>Auto耗时比Poco多50%左右。</li>
<li>Auto耗时是PocoFunc的两倍多。</li>
</ul>
</blockquote>
<h3 id="22-能不能用automapper生成委托来提高性能呢">2.2 能不能用AutoMapper生成委托来提高性能呢</h3>
<blockquote>
<ul>
<li>既可以说能也可以说不能。</li>
<li>说能是因为AutoMapper确实提供了该功能。</li>
<li>说不能是因为AutoMapper没打算给用户用。</li>
</ul>
</blockquote>
<h3 id="221-automapper生成委托有点麻烦">2.2.1 AutoMapper生成委托有点麻烦</h3>
<pre><code class="language-csharp">var configuration = _auto.ConfigurationProvider.Internal();
var mapRequest = new MapRequest(new TypePair(typeof(Customer), typeof(CustomerDTO)));
Func&lt;Customer, CustomerDTO, ResolutionContext, CustomerDTO&gt; autoFunc = configuration.GetExecutionPlan&lt;Customer, CustomerDTO&gt;(mapRequest);
</code></pre>
<p>作为对比PocoEmit.Mapper就简单的多了</p>
<pre><code class="language-csharp">Func&lt;Customer, CustomerDTO&gt; pocoFunc = PocoEmit.Mapper.Default.GetConvertFunc&lt;Customer, CustomerDTO&gt;();
</code></pre>
<h3 id="222-调用automapper生成的委托更麻烦">2.2.2 调用AutoMapper生成的委托更麻烦</h3>
<blockquote>
<ul>
<li>参数ResolutionContext没有公开的构造函数,也找不到公开的实例。</li>
<li>只能通过反射获得ResolutionContext的实例。</li>
</ul>
</blockquote>
<pre><code class="language-csharp">var field = typeof(AutoMapper.Mapper).GetField("_defaultContext", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
ResolutionContext resolutionContext = field.GetValue(_auto) as ResolutionContext;
</code></pre>
<h3 id="223-加入automapper生成委托再对比一下">2.2.3 加入AutoMapper生成委托再对比一下</h3>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right">Mean</th>
<th style="text-align: right">Error</th>
<th style="text-align: right">StdDev</th>
<th style="text-align: right">Median</th>
<th style="text-align: right">Ratio</th>
<th style="text-align: right">RatioSD</th>
<th style="text-align: right">Gen0</th>
<th style="text-align: right">Allocated</th>
<th style="text-align: right">Alloc Ratio</th>
</tr>
</thead>
<tbody>
<tr>
<td>Auto</td>
<td style="text-align: right">89.30 ns</td>
<td style="text-align: right">1.006 ns</td>
<td style="text-align: right">1.118 ns</td>
<td style="text-align: right">90.17 ns</td>
<td style="text-align: right">1.46</td>
<td style="text-align: right">0.03</td>
<td style="text-align: right">0.0260</td>
<td style="text-align: right">448 B</td>
<td style="text-align: right">1.08</td>
</tr>
<tr>
<td>AutoFunc</td>
<td style="text-align: right">56.04 ns</td>
<td style="text-align: right">0.103 ns</td>
<td style="text-align: right">0.119 ns</td>
<td style="text-align: right">56.03 ns</td>
<td style="text-align: right">0.91</td>
<td style="text-align: right">0.02</td>
<td style="text-align: right">0.0260</td>
<td style="text-align: right">448 B</td>
<td style="text-align: right">1.08</td>
</tr>
<tr>
<td>Poco</td>
<td style="text-align: right">61.31 ns</td>
<td style="text-align: right">1.036 ns</td>
<td style="text-align: right">1.194 ns</td>
<td style="text-align: right">61.25 ns</td>
<td style="text-align: right">1.00</td>
<td style="text-align: right">0.03</td>
<td style="text-align: right">0.0241</td>
<td style="text-align: right">416 B</td>
<td style="text-align: right">1.00</td>
</tr>
<tr>
<td>PocoFunc</td>
<td style="text-align: right">42.56 ns</td>
<td style="text-align: right">0.066 ns</td>
<td style="text-align: right">0.073 ns</td>
<td style="text-align: right">42.56 ns</td>
<td style="text-align: right">0.69</td>
<td style="text-align: right">0.01</td>
<td style="text-align: right">0.0223</td>
<td style="text-align: right">384 B</td>
<td style="text-align: right">0.92</td>
</tr>
</tbody>
</table>
<blockquote>
<ul>
<li>AutoMapper生成委托确实也快了不少。</li>
<li>从百分比来看即使不生成委托,AutoMapper也慢不了多少?没有数量级的区别,能忍? --- 反问句</li>
</ul>
</blockquote>
<h3 id="23-简单类型转化对比">2.3 简单类型转化对比</h3>
<blockquote>
<ul>
<li>User转UserDTO,只有两个简单属性</li>
</ul>
</blockquote>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right">Mean</th>
<th style="text-align: right">Error</th>
<th style="text-align: right">StdDev</th>
<th style="text-align: right">Ratio</th>
<th style="text-align: right">Gen0</th>
<th style="text-align: right">Allocated</th>
<th style="text-align: right">Alloc Ratio</th>
</tr>
</thead>
<tbody>
<tr>
<td>Auto</td>
<td style="text-align: right">35.436 ns</td>
<td style="text-align: right">0.0455 ns</td>
<td style="text-align: right">0.0505 ns</td>
<td style="text-align: right">1.57</td>
<td style="text-align: right">0.0019</td>
<td style="text-align: right">32 B</td>
<td style="text-align: right">0.50</td>
</tr>
<tr>
<td>AutoFunc</td>
<td style="text-align: right">4.159 ns</td>
<td style="text-align: right">0.0847 ns</td>
<td style="text-align: right">0.0906 ns</td>
<td style="text-align: right">0.18</td>
<td style="text-align: right">0.0019</td>
<td style="text-align: right">32 B</td>
<td style="text-align: right">0.50</td>
</tr>
<tr>
<td>Poco</td>
<td style="text-align: right">22.607 ns</td>
<td style="text-align: right">0.1754 ns</td>
<td style="text-align: right">0.1801 ns</td>
<td style="text-align: right">1.00</td>
<td style="text-align: right">0.0037</td>
<td style="text-align: right">64 B</td>
<td style="text-align: right">1.00</td>
</tr>
<tr>
<td>PocoFunc</td>
<td style="text-align: right">3.818 ns</td>
<td style="text-align: right">0.0176 ns</td>
<td style="text-align: right">0.0180 ns</td>
<td style="text-align: right">0.17</td>
<td style="text-align: right">0.0019</td>
<td style="text-align: right">32 B</td>
<td style="text-align: right">0.50</td>
</tr>
</tbody>
</table>
<blockquote>
<ul>
<li>Auto耗时是AutoFunc差不多十倍,差出一个数量级了(回答了前面的反问)</li>
<li>AutoFunc耗时比PocoFunc稍多,这说明AutoMapper复杂类型转化性能非常不好,简单类型转化可能还能凑合</li>
<li>关键是性能好生成的委托AutoMapper不给用啊,“婶可忍叔不可忍”啊!</li>
</ul>
</blockquote>
<h2 id="3-automapper生成的代码能通过代码审核吗">3. AutoMapper生成的代码能通过代码审核吗?</h2>
<h3 id="31-automapper官网那个例子生成以下代码">3.1 AutoMapper官网那个例子生成以下代码</h3>
<pre><code class="language-csharp">T __f&lt;T&gt;(System.Func&lt;T&gt; f) =&gt; f();
CustomerDTO _autoMap(Customer source, CustomerDTO destination, ResolutionContext context)
{
    return (source == null) ?
      (destination == null) ? (CustomerDTO)null : destination :
      __f(() =&gt; {
            CustomerDTO typeMapDestination = null;
            typeMapDestination = destination ?? new CustomerDTO();
            try
            {
                typeMapDestination.Id = source.Id;
            }
            catch (Exception ex)
            {
                throw TypeMapPlanBuilder.MemberMappingError(
                  ex,
                  default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
            }
            try
            {
                typeMapDestination.Name = source.Name;
            }
            catch (Exception ex)
            {
                throw TypeMapPlanBuilder.MemberMappingError(
                  ex,
                  default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
            }
            try
            {
                Address resolvedValue = null;
                Address mappedValue = null;
                resolvedValue = source.Address;
                mappedValue = (resolvedValue == null) ? (Address)null :
                  ((Func&lt;Address, Address, ResolutionContext, Address&gt;)((
                        Address source_1,
                        Address destination_1,
                        ResolutionContext context) =&gt; //Address
                        (source_1 == null) ?
                            (destination_1 == null) ? (Address)null : destination_1 :
                            __f(() =&gt; {
                              Address typeMapDestination_1 = null;
                              typeMapDestination_1 = destination_1 ?? new Address();
                              try
                              {
                                    typeMapDestination_1.Id = source_1.Id;
                              }
                              catch (Exception ex)
                              {
                                    throw TypeMapPlanBuilder.MemberMappingError(
                                        ex,
                                        default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
                              }
                              try
                              {
                                    typeMapDestination_1.Street = source_1.Street;
                              }
                              catch (Exception ex)
                              {
                                    throw TypeMapPlanBuilder.MemberMappingError(
                                        ex,
                                        default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
                              }
                              try
                              {
                                    typeMapDestination_1.City = source_1.City;
                              }
                              catch (Exception ex)
                              {
                                    throw TypeMapPlanBuilder.MemberMappingError(
                                        ex,
                                        default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
                              }
                              try
                              {
                                    typeMapDestination_1.Country = source_1.Country;
                              }
                              catch (Exception ex)
                              {
                                    throw TypeMapPlanBuilder.MemberMappingError(
                                        ex,
                                        default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
                              }
                              return typeMapDestination_1;
                            })))
                  .Invoke(
                        resolvedValue,
                        (destination == null) ? (Address)null :
                            typeMapDestination.Address,
                        context);
                typeMapDestination.Address = mappedValue;
            }
            catch (Exception ex)
            {
                throw TypeMapPlanBuilder.MemberMappingError(
                  ex,
                  default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
            }
            try
            {
                Address resolvedValue_1 = null;
                AddressDTO mappedValue_1 = null;
                resolvedValue_1 = source.HomeAddress;
                mappedValue_1 = (resolvedValue_1 == null) ? (AddressDTO)null :
                  ((Func&lt;Address, AddressDTO, ResolutionContext, AddressDTO&gt;)((
                        Address source_2,
                        AddressDTO destination_2,
                        ResolutionContext context) =&gt; //AddressDTO
                        (source_2 == null) ?
                            (destination_2 == null) ? (AddressDTO)null : destination_2 :
                            __f(() =&gt; {
                              AddressDTO typeMapDestination_2 = null;
                              typeMapDestination_2 = destination_2 ?? new AddressDTO();
                              try
                              {
                                    typeMapDestination_2.Id = source_2.Id;
                              }
                              catch (Exception ex)
                              {
                                    throw TypeMapPlanBuilder.MemberMappingError(
                                        ex,
                                        default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
                              }
                              try
                              {
                                    typeMapDestination_2.City = source_2.City;
                              }
                              catch (Exception ex)
                              {
                                    throw TypeMapPlanBuilder.MemberMappingError(
                                        ex,
                                        default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
                              }
                              try
                              {
                                    typeMapDestination_2.Country = source_2.Country;
                              }
                              catch (Exception ex)
                              {
                                    throw TypeMapPlanBuilder.MemberMappingError(
                                        ex,
                                        default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
                              }
                              return typeMapDestination_2;
                            })))
                  .Invoke(
                        resolvedValue_1,
                        (destination == null) ? (AddressDTO)null :
                            typeMapDestination.HomeAddress,
                        context);
                typeMapDestination.HomeAddress = mappedValue_1;
            }
            catch (Exception ex)
            {
                throw TypeMapPlanBuilder.MemberMappingError(
                  ex,
                  default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
            }
            try
            {
                Address[] resolvedValue_2 = null;
                AddressDTO[] mappedValue_2 = null;
                resolvedValue_2 = source.Addresses;
                mappedValue_2 = (resolvedValue_2 == null) ?
                  Array.Empty&lt;AddressDTO&gt;() :
                  __f(() =&gt; {
                        AddressDTO[] destinationArray = null;
                        int destinationArrayIndex = default;
                        destinationArray = new AddressDTO;
                        destinationArrayIndex = default(int);
                        int sourceArrayIndex = default;
                        Address sourceItem = null;
                        sourceArrayIndex = default(int);
                        while (true)
                        {
                            if ((sourceArrayIndex &lt; resolvedValue_2.Length))
                            {
                              sourceItem = resolvedValue_2;
                              destinationArray = ((Func&lt;Address, AddressDTO, ResolutionContext, AddressDTO&gt;)((
                                    Address source_2,
                                    AddressDTO destination_2,
                                    ResolutionContext context) =&gt; //AddressDTO
                                    (source_2 == null) ?
                                        (destination_2 == null) ? (AddressDTO)null : destination_2 :
                                        __f(() =&gt; {
                                          AddressDTO typeMapDestination_2 = null;
                                          typeMapDestination_2 = destination_2 ?? new AddressDTO();
                                          try
                                          {
                                                typeMapDestination_2.Id = source_2.Id;
                                          }
                                          catch (Exception ex)
                                          {
                                                throw TypeMapPlanBuilder.MemberMappingError(
                                                    ex,
                                                    default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
                                          }
                                          try
                                          {
                                                typeMapDestination_2.City = source_2.City;
                                          }
                                          catch (Exception ex)
                                          {
                                                throw TypeMapPlanBuilder.MemberMappingError(
                                                    ex,
                                                    default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
                                          }
                                          try
                                          {
                                                typeMapDestination_2.Country = source_2.Country;
                                          }
                                          catch (Exception ex)
                                          {
                                                throw TypeMapPlanBuilder.MemberMappingError(
                                                    ex,
                                                    default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
                                          }
                                          return typeMapDestination_2;
                                        })))
                              .Invoke(
                                    sourceItem,
                                    (AddressDTO)null,
                                    context);
                              sourceArrayIndex++;
                            }
                            else
                            {
                              goto LoopBreak;
                            }
                        }
                  LoopBreak:;
                        return destinationArray;
                  });
                typeMapDestination.Addresses = mappedValue_2;
            }
            catch (Exception ex)
            {
                throw TypeMapPlanBuilder.MemberMappingError(
                  ex,
                  default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
            }
            try
            {
                List&lt;Address&gt; resolvedValue_3 = null;
                List&lt;AddressDTO&gt; mappedValue_3 = null;
                resolvedValue_3 = source.WorkAddresses;
                mappedValue_3 = (resolvedValue_3 == null) ?
                  new List&lt;AddressDTO&gt;() :
                  __f(() =&gt; {
                        List&lt;AddressDTO&gt; collectionDestination = null;
                        List&lt;AddressDTO&gt; passedDestination = null;
                        passedDestination = (destination == null) ? (List&lt;AddressDTO&gt;)null :
                            typeMapDestination.WorkAddresses;
                        collectionDestination = passedDestination ?? new List&lt;AddressDTO&gt;();
                        collectionDestination.Clear();
                        List&lt;Address&gt;.Enumerator enumerator = default;
                        Address item = null;
                        enumerator = resolvedValue_3.GetEnumerator();
                        try
                        {
                            while (true)
                            {
                              if (enumerator.MoveNext())
                              {
                                    item = enumerator.Current;
                                    collectionDestination.Add(((Func&lt;Address, AddressDTO, ResolutionContext, AddressDTO&gt;)((
                                        Address source_2,
                                        AddressDTO destination_2,
                                        ResolutionContext context) =&gt; //AddressDTO
                                        (source_2 == null) ?
                                          (destination_2 == null) ? (AddressDTO)null : destination_2 :
                                          __f(() =&gt; {
                                                AddressDTO typeMapDestination_2 = null;
                                                typeMapDestination_2 = destination_2 ?? new AddressDTO();
                                                try
                                                {
                                                    typeMapDestination_2.Id = source_2.Id;
                                                }
                                                catch (Exception ex)
                                                {
                                                    throw TypeMapPlanBuilder.MemberMappingError(
                                                      ex,
                                                      default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
                                                }
                                                try
                                                {
                                                    typeMapDestination_2.City = source_2.City;
                                                }
                                                catch (Exception ex)
                                                {
                                                    throw TypeMapPlanBuilder.MemberMappingError(
                                                      ex,
                                                      default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
                                                }
                                                try
                                                {
                                                    typeMapDestination_2.Country = source_2.Country;
                                                }
                                                catch (Exception ex)
                                                {
                                                    throw TypeMapPlanBuilder.MemberMappingError(
                                                      ex,
                                                      default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
                                                }
                                                return typeMapDestination_2;
                                          })))
                                    .Invoke(
                                        item,
                                        (AddressDTO)null,
                                        context));
                              }
                              else
                              {
                                    goto LoopBreak_1;
                              }
                            }
                        LoopBreak_1:;
                        }
                        finally
                        {
                            enumerator.Dispose();
                        }
                        return collectionDestination;
                  });
                typeMapDestination.WorkAddresses = mappedValue_3;
            }
            catch (Exception ex)
            {
                throw TypeMapPlanBuilder.MemberMappingError(
                  ex,
                  default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);
            }
            return typeMapDestination;
      });
}   
</code></pre>
<h3 id="32-以下是pocoemitmapper生成的代码">3.2 以下是PocoEmit.Mapper生成的代码</h3>
<pre><code class="language-csharp">CustomerDTO _pocoConvert(Customer source)
{
    CustomerDTO dest = null;
    if ((source != (Customer)null))
    {
      dest = new CustomerDTO();
      Address member0 = null;
      Address member1 = null;
      Address[] member2 = null;
      List&lt;Address&gt; member3 = null;
      dest.Id = source.Id;
      dest.Name = source.Name;
      member0 = source.Address;
      if ((member0 != null))
      {
            dest.Address = member0;
      }
      member1 = source.HomeAddress;
      if ((member1 != null))
      {
            // { The block result will be assigned to `dest.HomeAddress`
            AddressDTO dest_1 = null;
            if ((member1 != (Address)null))
            {
                dest_1 = new AddressDTO();
                dest_1.Id = member1.Id;
                dest_1.City = member1.City;
                dest_1.Country = member1.Country;
            }
            dest.HomeAddress = dest_1;
            // } end of block assignment;
      }
      member2 = source.Addresses;
      if ((member2 != null))
      {
            // { The block result will be assigned to `dest.Addresses`
            int count = default;
            AddressDTO[] dest_2 = null;
            int index = default;
            Address sourceItem = null;
            count = member2.Length;
            dest_2 = new AddressDTO;
            index = 0;
            while (true)
            {
                if ((index &lt; count))
                {
                  sourceItem = member2;
                  // { The block result will be assigned to `dest_2`
                  AddressDTO dest_3 = null;
                  if ((sourceItem != (Address)null))
                  {
                        dest_3 = new AddressDTO();
                        dest_3.Id = sourceItem.Id;
                        dest_3.City = sourceItem.City;
                        dest_3.Country = sourceItem.Country;
                  }
                  dest_2 = dest_3;
                  // } end of block assignment
                  index++;
                }
                else
                {
                  goto forLabel;
                }
            }
            forLabel:;
            dest.Addresses = dest_2;
            // } end of block assignment;
      }
      member3 = source.WorkAddresses;
      if ((member3 != null))
      {
            // { The block result will be assigned to `dest.WorkAddresses`
            List&lt;AddressDTO&gt; dest_4 = null;
            dest_4 = new List&lt;AddressDTO&gt;(member3.Count);
            dest_4;
            int index_1 = default;
            int len = default;
            index_1 = 0;
            len = member3.Count;
            while (true)
            {
                if ((index_1 &lt; len))
                {
                  Address sourceItem_1 = null;
                  AddressDTO destItem = null;
                  sourceItem_1 = member3;
                  // { The block result will be assigned to `destItem`
                        AddressDTO dest_5 = null;
                        if ((sourceItem_1 != (Address)null))
                        {
                            dest_5 = new AddressDTO();
                            dest_5.Id = sourceItem_1.Id;
                            dest_5.City = sourceItem_1.City;
                            dest_5.Country = sourceItem_1.Country;
                        }
                        destItem = dest_5;
                        // } end of block assignment;
                  dest_4.Add(destItem);
                  index_1++;
                }
                else
                {
                  goto forLabel_1;
                }
            }
            forLabel_1:;
            dest.WorkAddresses = dest_4;
            // } end of block assignment;
      }
      CustomerConvertBench.ConvertAddressCity(
            source,
            dest);
    }
    return dest;
}
</code></pre>
<h3 id="33-简单对比如下">3.3 简单对比如下</h3>
<blockquote>
<ul>
<li>AutoMapper生成代码三百多行,PocoEmit.Mapper一百多行,AutoMapper代码量是两倍以上</li>
<li>AutoMapper生成大量try catch,哪怕是int对int赋值也要try</li>
<li>AutoMapper用迭代器Enumerator访问列表,PocoEmit.Mapper用索引器</li>
<li>AutoMapper这些区别应该是导致性能差的部分原因</li>
</ul>
</blockquote>
<h3 id="34-如何获取automapper生成的代码">3.4 如何获取AutoMapper生成的代码</h3>
<pre><code class="language-csharp">LambdaExpression expression = _auto.ConfigurationProvider.BuildExecutionPlan(typeof(Customer), typeof(CustomerDTO));   
</code></pre>
<h4 id="341-如果要查看更可读的代码推荐使用fastexpressioncompiler">3.4.1 如果要查看更可读的代码推荐使用FastExpressionCompiler</h4>
<blockquote>
<ul>
<li>可以使用nuget安装</li>
<li>前面的例子就是使用FastExpressionCompiler再手动整理了一下</li>
</ul>
</blockquote>
<pre><code class="language-csharp">string code = FastExpressionCompiler.ToCSharpPrinter.ToCSharpString(expression);
</code></pre>
<h4 id="342-pocoemit获取生成代码更简单">3.4.2 PocoEmit获取生成代码更简单</h4>
<pre><code class="language-csharp">Expression&lt;Func&lt;Customer, CustomerDTO&gt;&gt; expression =PocoEmit.Mapper.Default.BuildConverter&lt;Customer, CustomerDTO&gt;();
string code = FastExpressionCompiler.ToCSharpPrinter.ToCSharpString(expression);
</code></pre>
<h4 id="343-pocoemit生成代码扩展性">3.4.3 PocoEmit生成代码扩展性</h4>
<blockquote>
<ul>
<li>PocoEmit可以获取委托表达式自己来编译委托</li>
<li>PocoEmit通过PocoEmit.Builders.Compiler.Instance来编译,可以对Instance进行覆盖来扩展</li>
<li>通过实现Compiler类来扩展,只需要重写CompileFunc和CompileAction两个方法</li>
<li>可以使用FastExpressionCompiler来实现Compiler类</li>
</ul>
</blockquote>
<h2 id="4-automapper枚举逻辑问题">4. AutoMapper枚举逻辑问题</h2>
<pre><code class="language-csharp">public enum MyColor
{
    None = 0,
    Red = 1,
    Green = 2,
    Blue = 3,
}
ConsoleColor color = ConsoleColor.DarkBlue;
// Red
MyColor autoColor = _auto.Map&lt;ConsoleColor, MyColor&gt;(color);
// None
MyColor pocoColor = PocoEmit.Mapper.Default.Convert&lt;ConsoleColor, MyColor&gt;(color);
</code></pre>
<blockquote>
<ul>
<li>AutoMapper先按枚举名转化,失败再按值转化,不支持的DarkBlue被AutoMapper转化为Red</li>
<li>不同类型的枚举值转化没有意义,定义枚举可以不指定值</li>
<li>AutoMapper这完全是犯了画蛇添足的错误</li>
<li>AutoMapper还有哪些槽点欢迎大家在评论区指出</li>
</ul>
</blockquote>
<h2 id="5-pocoemit可扩展架构">5. PocoEmit可扩展架构</h2>
<h3 id="51-nuget安装pocoemit可获得基础功能">5.1 nuget安装PocoEmit可获得基础功能</h3>
<blockquote>
<ul>
<li>通过PocoEmit可以读写实体的属性</li>
<li>PocoEmit可以通过PocoEmit.Poco转化基础类型和枚举</li>
<li>PocoEmit.Poco支持注册转化表达式</li>
</ul>
</blockquote>
<h3 id="52-nuget安装pocoemitmapper获得更多功能">5.2 nuget安装PocoEmit.Mapper获得更多功能</h3>
<blockquote>
<ul>
<li>PocoEmit.Mapper可以支持PocoEmit.Poco的所有功能</li>
<li>PocoEmit.Mapper可以支持自定义实体类型(不支持集合(含数组、列表及字典)成员)的转化和复制</li>
</ul>
</blockquote>
<h3 id="53-nuget安装pocoemitcollections扩展集合功能">5.3 nuget安装PocoEmit.Collections扩展集合功能</h3>
<blockquote>
<ul>
<li>通过UseCollection扩展方法给PocoEmit.Mapper增加集合功能</li>
<li>扩展后PocoEmit.Mapper支持集合(含数组、列表及字典)的转化和复制</li>
<li>支持实体类型包含集合成员的转化和复制</li>
<li>嫌麻烦的同学可以直接安装PocoEmit.Collections并配置UseCollection</li>
</ul>
</blockquote>
<p>如何使用PocoEmit.Mapper替代AutoMapper请移步看下一篇 https://www.cnblogs.com/xiangji/p/19062936</p>
<p>源码托管地址: https://github.com/donetsoftwork/MyEmit ,也欢迎大家直接查看源码。<br>
gitee同步更新:https://gitee.com/donetsoftwork/MyEmit</p>
<p>如果大家喜欢请动动您发财的小手手帮忙点一下Star。</p><br><br>
来源:https://www.cnblogs.com/xiangji/p/19059979
頁: [1]
查看完整版本: 婶可忍叔不可忍的AutoMapper,你还用吗?