假设我们有这对结构,它们是广泛使用的交换格式的一部分(因此,我们无法修改源代码-理想情况下不希望:我们不尝试更改数据本身):
struct Vector2 { public int x; public int y; }
struct Vector3 { public int x; public int y; public int z; }
是一个类,其核心是一个或另一个的列表,并且包含许多算法,对于任何一个结构,该算法几乎都是相同的,但是必须引用3元素结构中的额外成员({{ }}):
z
...如何正确实现处理它们的方法套件?例如:
public class Mesh<T>
{
private List<T> _myVectors;
}
尤其要注意:特定于结构的方法已经存在(数以百万计),由缺少任何内置泛型的API提供。因此,例如我们可以放心地编写一种算法来使用public int Average()
{
// if Vector2:
int sum = 0, count = 0;
foreach( var v2 in _MyVectors )
{
sum += v2.x + v2.y;
count++;
}
// if Vector3:
int sum = 0, count = 0;
foreach( var v3 in _MyVectors )
{
sum += v3.x + v3.y + v3.z;
count++;
}
return sum/count;
}
中的方法,因为知道源代码的一个副本(但使用泛型)将以任何一种方式绑定到可接受的实现:
FOREIGN_API
我想缠住这个问题的大概是:
public float FOREIGN_API_Average( Vector2 input );
public float FOREIGN_API_Average( Vector3 input );
概念部分,我不知道该如何使用C#。// if Vector2:
约束使用类(Mesh),然后从那里开始。但这对于结构是不可能的。where T : BaseVector
,Vector1
,Vector2
,Vector3
重新实现所有功能。 。我不想复制/粘贴很多代码!答案 0 :(得分:1)
确实让您羡慕C ++及其愚蠢的性感模板,不是吗?
首先进行一些假设(如果错误,请更正):
您已经说过,网格类型可以有四个不同的列表,因此我假设其签名为Mesh<T1, T2, T3, T4>
。我还假设您控制此类型,但不能控制VectorN
类型。
问题是您缺乏对Vector的任何一般支持,并且不能以任何方式对它们使用多态。如您所说,将它们包装在接口中或引入自定义类作为包装器会降低性能。
因此,您要做的是对双调度进行修改-根据其参数类型调用其他方法。
想到的最简单的事情是现有FOREIGN_API
调用的静态包装:
public static class VectorExtensions
{
public static int Sum<TVector>(this IEnumerable<TVector> vectors)
{
var type = typeof(TVector);
if (type == typeof(Vector1))
{
return FOREIGN_API.Sum((IEnumerable<Vector1>)vectors);
}
else if (type == typeof(Vector2))
{
return FOREIGN_API.Sum((IEnumerable<Vector2>)vectors);
}
else if (...) // etc.
throw new ArgumentException($"Invalid type of vector {typeof(TVector).Name}.");
}
}
然后,在网格上实现Average
很容易(我假设平均值是所有列表的平均值):
public class Mesh<T1, T2, T3, T4>
{
private List<T1> _myVectors1;
private List<T2> _myVectors2;
private List<T3> _myVectors3;
private List<T4> _myVectors4;
public float Average()
{
var sum1 = _myVectors1.Sum();
var sum2 = _myVectors2.Sum();
var sum3 = _myVectors3.Sum();
var sum4 = _myVectors4.Sum();
return (float)(sum1 + sum2 + sum3 + sum4) /
(_myVectors1.Count + _myVectors2.Count + _myVectors3.Count + _myVectors4.Count);
}
}
这种类型的类型检查应该很快,因为C#会大量优化typeof
调用。
有一种更简单的编写方法,涉及dynamic
:
public static class VectorExtensions
{
public static int Sum<TVector>(this IEnumerable<TVector> vectors) =>
FOREIGN_API.Sum((dynamic)vectors);
}
dynamic
基础结构的速度也比许多人预期的要高,因为缓存,因此您可能想先尝试一下此解决方案,然后仅在诊断出性能问题时再考虑其他问题。如您所见,这只需要少量的代码即可尝试。
================================================ =============================
现在让我们假设我们正在寻找性能最好的方法。我非常确信,没有完全避免运行时类型检查的方法。在上面的案例中,每个方法调用只有很少的类型检查。除非您数百万次调用Mesh<,,,>
方法,否则应该没问题。但是,假设您可能想要这样做,那么有一种方法可以帮助我们摆脱困境。
这个想法是在实例化网格物体时执行所需的所有类型检查。让我们定义助手类型,我们将为VectorOperationsN
类型中所有可能的N
调用VectorN
。它将实现一个接口IVectorOperations<TVector>
,该接口将定义您想要的基本矢量操作。现在,以Sum
为例,介绍一个或多个向量:
public interface IVectorOperations<TVector>
{
public int Sum(TVector vector);
public int Sum(IEnumerable<TVector> vectors);
}
public class VectorOperations1 : IVectorOperations<Vector1>
{
public int Sum(Vector1 vector) => vector.x;
public int Sum(IEnumerable<Vector1> vectors) => vectors.Sum(v => Sum(v));
}
public class VectorOperations2 : IVectorOperations<Vector2>
{
public int Sum(Vector2 vector) => vector.x + vector.y;
public int Sum(IEnumerable<Vector2> vectors) => vectors.Sum(v => Sum(v));
}
现在,我们需要一种获取适当实现的方法-这将涉及类型检查:
public static class VectorOperations
{
public static IVectorOperations<TVector> GetFor<TVector>()
{
var type = typeof(TVector);
if (type == typeof(Vector1))
{
return (IVectorOperations<TVector>)new VectorOperations1();
}
else if (...) // etc.
throw new ArgumentException($"Invalid type of vector {typeof(TVector).Name}.");
}
}
现在,当我们创建网格时,我们将获得适当的实现,然后在我们的方法中全部使用它:
public class Mesh<T1, T2, T3, T4>
{
private List<T1> _myVectors1;
private List<T2> _myVectors2;
private List<T3> _myVectors3;
private List<T4> _myVectors4;
private readonly IVectorOperations<T1> _operations1;
private readonly IVectorOperations<T2> _operations2;
private readonly IVectorOperations<T3> _operations3;
private readonly IVectorOperations<T4> _operations4;
public Mesh()
{
_operations1 = VectorOperations.GetFor<T1>();
_operations2 = VectorOperations.GetFor<T2>();
_operations3 = VectorOperations.GetFor<T3>();
_operations4 = VectorOperations.GetFor<T4>();
}
public float Average()
{
var sum1 = _operations1.Sum(_myVectors1);
var sum2 = _operations2.Sum(_myVectors2);
var sum3 = _operations3.Sum(_myVectors3);
var sum4 = _operations4.Sum(_myVectors4);
return (float)(sum1 + sum2 + sum3 + sum4) /
(_myVectors1.Count + _myVectors2.Count + _myVectors3.Count + _myVectors4.Count);
}
}
这仅在实例化网格时有效并且进行类型检查。成功!但是我们可以使用两个技巧来进一步优化。
一个,我们不需要IVectorOperations<TVector>
实现的新实例。我们可以使它们成为单例,并且对于一种类型的向量,永远不要实例化多个对象。这是绝对安全的,因为无论如何这些实现始终都是无状态的。
public static class VectorOperations
{
private static VectorOperations1 Implementation1 = new VectorOperations1();
private static VectorOperations2 Implementation2 = new VectorOperations2();
... // etc.
public static IVectorOperations<TVector> GetFor<TVector>()
{
var type = typeof(TVector);
if (type == typeof(Vector1))
{
return (IVectorOperations<TVector>)Implementation1;
}
else if (...) // etc.
throw new ArgumentException($"Invalid type of vector {typeof(TVector).Name}.");
}
}
第二,我们每次实例化一个新的网格时并不需要进行类型检查。很容易看出,对于具有相同类型参数的网格类型的每个对象,实现都保持不变。就单个封闭的通用类型而言,它们是静态的。因此,我们真的可以将它们设为静态:
public class Mesh<T1, T2, T3, T4>
{
private List<T1> _myVectors1;
private List<T2> _myVectors2;
private List<T3> _myVectors3;
private List<T4> _myVectors4;
private static readonly IVectorOperations<T1> Operations1 =
VectorOperations.GetFor<T1>();
private static readonly IVectorOperations<T2> Operations2 =
VectorOperations.GetFor<T2>();
private static readonly IVectorOperations<T3> Operations3 =
VectorOperations.GetFor<T3>();
private static readonly IVectorOperations<T4> Operations4 =
VectorOperations.GetFor<T4>();
public float Average()
{
var sum1 = Operations1.Sum(_myVectors1);
var sum2 = Operations2.Sum(_myVectors2);
var sum3 = Operations3.Sum(_myVectors3);
var sum4 = Operations4.Sum(_myVectors4);
return (float)(sum1 + sum2 + sum3 + sum4) /
(_myVectors1.Count + _myVectors2.Count + _myVectors3.Count + _myVectors4.Count);
}
}
这样,如果存在N
个不同的向量类型,我们将只实例化实现N
的{{1}}个对象,并执行与存在不同网格类型一样多的附加类型检查,因此最多IVectorOperations<>
。各个网格对象不会占用任何额外的内存,但是最多有4^N
个引用指向矢量操作实现。
这仍然迫使您针对不同类型四次执行所有矢量操作。但是请注意,现在您已经解锁了所有选项-您拥有一个通用接口,该接口取决于您控制的4^N * 4
类型。 TVector
实现中的任何技巧都是允许的。在通过VectorOperations
接口与Mesh
分离的同时,您可以在其中灵活地进行操作。
哇,答案很长。感谢您参加我的TED演讲!
答案 1 :(得分:0)
(我不认为这可行,但这是我一开始尝试的方向-欢迎发表评论,也许会激发其他人提供更好的答案:)
我以为我可以做类似的事情(如果我没记错的话,在C ++中是可能的,并且C#在完全普通的情况下没有直接等价物,但我认为在这样的简单情况下可能有一个等价物):>
public class Mesh<T1,T2>
{
// This class is basically going to fail at runtime:
// it cannot/will not prevent you from instancing it
// as - say - a Mesh<string,int> - which simply cannot
// be sensibly implemented.
//
// So: many methods will throw Exceptions - but some can be implemented
// (and hence: shared amongst all the other variants of the class)
public List<T1> internalList;
public int CountElements<List<T1>>() { return internalList.Count; }
public int DoSomethingToList1<T1>() { ... }
}
public class Mesh<Vector2,T2>
{
// Now we're saying: HEY compiler! I'll manually override the
// generic instance of Mesh<T1,T2> in all cases where the
// T1 is a Vector2!
public int DoSomethingToList1<Vector2>() { ... }
}
或者另一种尝试找到在语法上有效的方法来做同样的事情(参见@Gserg对主要问题的评论)-但显然这失败了,因为C#编译器禁止任意类型转换:
private List<T1> data;
public void Main()
{
if( typeof(T1) == typeof(Vector2) )
Main( (List<Vector2>) data );
else if( typeof(T1) == typeof(Vector3) )
Main( (List<Vector3>) data );
}
public void Main( List<Vector2> dataVector2s )
{
...
}
public void Main( List<Vector3> dataVector3s )
{
...
}
答案 2 :(得分:0)
我不确定这是否是您想要的,但是也许您可以通过一些运行时编译来解决。例如,您可以生成一个对结构的字段求和的委托;
public Func<T, int> Sum { get; private set; }
public void Compile()
{
var parm = Expression.Parameter(typeof(T), "parm");
Expression sum = null;
foreach(var p in typeof(T).GetFields())
{
var member = Expression.MakeMemberAccess(parm, p);
sum = sum == null ? (Expression)member : Expression.Add(sum, member);
}
Sum = Expression.Lambda<Func<T, int>>(sum, parm).Compile();
}
或者只是将结构转换为其他易于枚举的枚举的方法。
答案 3 :(得分:-2)
我的建议是让Vector2和Vector3带来他们自己的处理方法。接口是您要寻找的机器人:
显示过程的合适名称为“ Sumable”。
这些可能是Vector struct的内置实现。当然那两个不能被继承。但是MVVM的处理方式是:“如果您无法继承或修改它,请将其包装为您可以 继承和修改的东西。”
您只需要做一个简单的包装程序(可以是结构或类),即可围绕实现接口的向量之一。
另一个选择是使用LINQ进行处理。如果这只是一个一次性的过程,那么它通常要轻得多,然后要一路继承到继承,类,接口等。