【问题标题】:c# generic with slight difference for types?c# 泛型与类型略有不同?
【发布时间】:2016-03-20 23:55:16
【问题描述】:

注意两个扩展,一个用于 float,一个用于 Vector3。

请注意,var( 调用中只有细微差别。

在 c# 中,这些可以写成一个泛型吗?

我的问题的本质是:

在泛型中,你可以根据类型的性质进行分支吗?

public static IEnumerator Tweeng( this float duration,
         System.Action<float> vary, float aa, float zz )
{
    float sT = Time.time;
    float eT = sT + duration;

    while (Time.time < eT)
    {   
        float t = (Time.time-sT)/duration;
        vary( Mathf.SmoothStep(aa,zz, t) ); // slight difference here
        yield return null;
    }

    vary(zz);
}

public static IEnumerator Tweeng( this float duration,
      System.Action<Vector3> vary, Vector3 aa, Vector3 zz )
{
    float sT = Time.time;
    float eT = sT + duration;

    while (Time.time < eT)
    {
        float t = (Time.time-sT)/duration;
        vary( Vector3.Lerp(aa,zz, t) ); // slight difference here
        yield return null;
    }

    vary(zz);
}

(顺便说一句,任何 c# 大师都在阅读,代码示例在 Unity 中,您可以在协程中访问框架系统。)

对于任何 Unity 开发人员阅读,您如何调用 Tweeng 的示例

// tweeng z to 20 degrees in .12 seconds
StartCoroutine(.12f.Tweeng( (t)=>transform.Eulers(0f,0f,t), 0f,20f) );
// fade in alpha in .75 seconds
StartCoroutine(.75f.Tweeng( (u)=>{c.a=u;s.color=c;}, 0f,1f) );

(如果您是 Unity 新手并且不熟悉扩展的基本概念,这里是 intro。)

【问题讨论】:

  • 是的,您可以添加一个通用参数T (float/Vector3),然后将Lerp/SmoothStep 作为Func&lt;T, T, T, float&gt; 传入
  • 啊,基本上添加了另一个参数。让扩展解决这个问题会很棒,我想可以将该部门向下推到另一个通用函数

标签: c# generics


【解决方案1】:

如果您在调用 var 操作(您应该重命名,因为 var 是 C# 关键字)之前执行转换,则可以这样做。

这是您可以采取的一种方法:

public static IEnumerator Tweeng<T>(
    this float duration
,   System.Action<T> varAction
,   T aa
,   T zz
) {
    Func<T,T,float,T> transform = MakeTransform<T>();
    float sT = Time.time;
    float eT = sT + duration;
    while (Time.time < eT) {   
        float t = (Time.time-sT)/duration;
        varAction(transform(aa, zz, t));
        yield return null;
    }
    varAction(zz);
}

private static Func<T,T,float,T> MakeTransform<T>() {
    if (typeof(T) == typeof(float)) {
        Func<float, float, float, float> f = Mathf.SmoothStep;
        return (Func<T,T,float,T>)(Delegate)f;
    }
    if (typeof(T) == typeof(Vector3)) {
        Func<Vector3, Vector3, float, Vector3> f = Vector3.Lerp;
        return (Func<T,T,float,T>)(Delegate)f;
    }
    throw new ArgumentException("Unexpected type "+typeof(T));
}

它甚至可以内联完成:

public static IEnumerator DasTweeng<T>( this float duration, System.Action<T> vary, T aa, T zz )
    {
    float sT = Time.time;
    float eT = sT + duration;

    Func<T,T,float,T> step;

    if (typeof(T) == typeof(float))
        step = (Func<T,T,float,T>)(Delegate)(Func<float, float, float, float>)Mathf.SmoothStep;
    else if (typeof(T) == typeof(Vector3))
        step = (Func<T,T,float,T>)(Delegate)(Func<Vector3, Vector3, float, Vector3>)Vector3.Lerp;
    else
        throw new ArgumentException("Unexpected type "+typeof(T));

    while (Time.time < eT)
        {
        float t = (Time.time-sT)/duration;
        vary( step(aa,zz, t) );
        yield return null;
        }
    vary(zz);
    }

也许更自然的成语是

    Delegate d;

    if (typeof(T) == typeof(float))
        d = (Func<float, float, float, float>)Mathf.SmoothStep;
    else if (typeof(T) == typeof(Vector3))
        d = (Func<Vector3, Vector3, float, Vector3>)Vector3.Lerp;
    else
        throw new ArgumentException("Unexpected type "+typeof(T));

    Func<T,T,float,T> step = (Func<T,T,float,T>)d;

【讨论】:

  • @JoeBlow 你可以通过if (typeof(T) == typeof(float))... 来打开类型(我很抱歉我上面的代码中的错误,现在已经修复了)。您当然可以在循环内执行此操作,但是您必须在每次迭代中做出相同的决定。预先存储 Func 可以让您执行一次 if,然后在每次迭代中重复使用结果。
  • 对,但我的意思是,可以将MakeTransform 中的代码简单地放在Tweeng 中(当然,在顶部以避免每次循环都这样做)...我会尝试一下!
  • @JoeBlow 绝对可以,可以在Tweeng&lt;T&gt; 中内联该代码。我把它分开是因为对我来说typeof(T) 上的条件有点“不干净”,而Tweeng&lt;T&gt; 的其余部分很好而且干净。
  • @JoeBlow 请尝试使用上次编辑的显式转换来修复。
  • @JoeBlow 编辑应该可以工作,或者至少现在可以编译。
【解决方案2】:

您可以按如下方式定义您的方法:

public static IEnumerator Tweeng<T>(this float duration,
         System.Action<T> var, T aa, T zz, Func<T,T,float,T> thing)
{
    float sT = Time.time;
    float eT = sT + duration;

    while (Time.time < eT)
    {
        float t = (Time.time - sT) / duration;
        var(thing(aa, zz, t));
        yield return null;
    }

    var(zz);
}

然后使用它:

float a = 5;
float b = 0;
float c = 0;
a.Tweeng(q => {}, b, c, Mathf.SmoothStep);

或者:

float a = 0;
Vector3 b = null;
Vector3 c = null;
a.Tweeng(q => {}, b, c, Vector3.Lerp);

或者,如果你想摆脱方法传递,你可以用简单的重载来处理它:

public static IEnumerator Tweeng(this float duration, System.Action<float> var, float aa, float zz)
{
    return Tweeng(duration, var, aa, zz, Mathf.SmoothStep);
}
public static IEnumerator Tweeng(this float duration, System.Action<Vector3> var, Vector3 aa, Vector3 zz)
{
    return Tweeng(duration, var, aa, zz, Vector3.Lerp);
}

private static IEnumerator Tweeng<T>(this float duration,
         System.Action<T> var, T aa, T zz, Func<T,T,float,T> thing)
{
    float sT = Time.time;
    float eT = sT + duration;

    while (Time.time < eT)
    {
        float t = (Time.time - sT) / duration;
        var(thing(aa, zz, t));
        yield return null;
    }

    var(zz);
}

然后使用它:

float a = 5;
float b = 0;
float c = 0;
a.Tweeng(q => {}, b, c);

或者:

float a = 0;
Vector3 b = null;
Vector3 c = null;
a.Tweeng(q => {}, b, c);


在 LINQPad 中编译/不统一的存根方法:
public class Mathf { public static float SmoothStep(float aa, float zz, float t) => 0; }
public class Time { public static float time => DateTime.Now.Ticks; }
public class Vector3 { public static Vector3 Lerp(Vector3 aa, Vector3 zz, float t) => null; }

【讨论】:

    【解决方案3】:

    我喜欢 Tweeng 的东西,但是如果 Coroutine 只能用于 MonoBehaviours,为什么还要扩展浮动呢?你应该为 MonoBehaviour 做扩展,例如我做了一个扩展来做插值:

    public static void _Interpolate(this MonoBehaviour monoBehaviour, float duration,
        Action<float, bool> callback, float from, float to, Interpolator interpolator)
    {
        monoBehaviour.StartCoroutine(ExecuteInterpolation(interpolator, duration, callback, from, to));
    }
    

    所以我刚刚在扩展中启动了一个协程:

    private static IEnumerator ExecuteInterpolation(Interpolator interpolator, float duration,
        Action<float, bool> callback, float from, float to)
    {
        float sT = Time.time;
        float eT = sT + duration;
        bool hasFinished = false;
        while (Time.time < eT)
        {
            float t = (Time.time - sT) / duration;
    // ----> my logic here with callback(to, false)
            yield return null;
        }
    
        hasFinished = true;
        callback(to, hasFinished);
    }
    

    请注意,我有一个布尔值表示插值已完成,这是因为依靠浮点比较来检查流的结束不是最佳实践,如果它舍入最大结果,则在我们要去的最后一个结果之前让最后一次交互的回调被调用两次。

    【讨论】:

    • 嘿费尔南多!当然,您可以“重新构建”任何您认为合适的扩展,它只是语法糖果——它没有现实。真的“Tweeng”按原样被广泛使用。 (例如,stackoverflow.com/a/37228628/294884)因为它是动画,所以(对我而言)它是“大约”一个时间,所以(对我来说)这是语法的逻辑“基础”。正如您所提到的,它只能用于 MonoBehavior - 自然,Unity 中的许多/大多数事情都是如此。真的,它只是一个约定俗成的秒。如果愿意,您当然可以按不同的顺序使用参数。享受吧!
    • 嗨 Fattie,“Tweeng”本身是一个非常有用的想法,我的代码只不过是 Monobehaviour 的 Tweeng 而不是 float,我可以使用 MonoB 重构 Tweeng。尽管如此,不仅语法不适合放置 .75.Tweeng(params[]) ,而且一旦动作只能在 MonoB 中执行,它也不正确。更好的函数描述自己的代码是最好的,创建一个没有定义的扩展,糟糕的语法和脱离上下文并不好,是一个快速的技巧,但不是一个好的解决方案。(尊重,我不想与您争论,但只需指出我发现的此解决方案的弱点)
    • 当然@fernanobonet,我完全明白你的意思!!我恭敬地不同意,手术发生在“on”秒,所以对我来说这很自然——但我完全明白你在说什么!!!我只想说,在统一代码中看到浮动扩展是很常见的,所以即使你不同意 - 你也可能在其他代码库中看到它!呵!干杯!!!
    • 不反对扩展 float 或任何其他原语,这与上下文、用法和命名有关,但我明白了你的意思。如果你明白了我的意思还是不同意,soooo...我更尊重哈哈哈,这个疯狂的世界人们只是大声说出自己的意见,而不考虑其他,很少有像这样的讨论,不是吗? xD
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-15
    • 1970-01-01
    • 1970-01-01
    • 2010-09-20
    相关资源
    最近更新 更多