考虑下面的C#程序,我在codegolf上提交它作为创建循环而没有循环的答案:
class P{
static int x=0;
~P(){
System.Console.WriteLine(++x);
new P();
}
static void Main(){
new P();
}
}
Run Code Online (Sandbox Code Playgroud)
在我的检查中,这个程序看起来像一个无限循环,但似乎运行了几千次迭代,然后程序成功终止而没有错误(没有抛出错误).P最终的终结者不会被称为规范违规吗?
显然这是一个愚蠢的代码,应该永远不会出现,但我很好奇该程序如何完成.
原始代码高尔夫邮件:: https://codegolf.stackexchange.com/questions/33196/loop-without-looping/33218#33218
在.NET中,引用类型数组是共变体.这被认为是一个错误.但是,我不明白为什么这么糟糕考虑以下代码:
string[] strings = new []{"Hey there"};
object[] objects = strings;
objects[0] = new object();
Run Code Online (Sandbox Code Playgroud)
哦,这个编译并在运行时失败.当我们试图将一个对象粘贴到一个字符串[]中时.好的,我同意臭,但是T []扩展了Array并且还实现了IList(并且IList<T>,我想知道它是否实现了IList<BaseType> ......>.Array和IList都允许我们犯同样的可怕错误.
string[] strings = new []{"Hey there"};
Array objects = strings;
objects.SetValue(new object(),new[]{0});
Run Code Online (Sandbox Code Playgroud)
IList版本
string[] strings = new []{"Hey there"};
IList objects = strings;
objects[0] = new object();
Run Code Online (Sandbox Code Playgroud)
T []类由CLR生成,并且必须包含对set_Item方法等效的类型检查(数组实际上没有一个).
是否担心设置为T []必须在运行时进行类型检查(这违反了编译时期望的类型安全性)?当有相同的方法通过上面提供的手段射击自己的脚时,为什么它被认为有害于阵列显示这种属性?
使用F#,我理解您可以使用inline关键字在呼叫站点执行类型特化.那是::
val inline (+) : ^a -> ^b -> ^c
when (^a or ^b) : (static member (+) : ^a * ^b -> ^c)
Run Code Online (Sandbox Code Playgroud)
约束,^a或者^b必须有一个静态成员op_Addition,或者一个内置的原语,可以用来填补空白.
因此,如果你有一个方法有一个+,你传入一个int和一个short作为参数,它解包+指令使用内置的原语为int,如果你传入一个浮点数和一个字节,它使用浮点数原始加法操作码.
这在编译时是如何完成的?你怎么能在CLR中有一个方法根据类型切换它使用的操作码或方法?
Reflection.Emit可以实现这种行为吗?我理解内联是在调用站点执行的,这是否意味着代码不能与C#一起使用?
通常你想发送多个值,但由于使用率低(即它只在一两个地方使用),很难证明创建一个新类型的合理性.
该Tuple<...>和KeyValuePair<,>类型是非常有用的,但不是他们真正的语言支持.
好吧,一种用于元组列表的好方法是创建一个扩展List并添加自定义add方法的类型:例如
public class TupleList<T1,T2> : List<Tuple<T1,T2>>{
public void Add(T1 key, T2 value){
base.Add(Tuple.Create(key, value));
}
}
Run Code Online (Sandbox Code Playgroud)
这意味着如果我有一个方法IEnumerable<Tuple<int,string>>,我可以使用以下代码快速建立列表,如下::
Foo(new TupleList<int,string>{{1,"one"},{2,"two"},{3,"three"}});
Run Code Online (Sandbox Code Playgroud)
这使得将值变为元组列表变得更容易,因为我们不必经常说Tuple.Create,并且几乎使我们获得了很好的函数式语言语法.
但是当使用元组时,将它展开到不同的组件中是很有用的.这方面的扩展方法可能有用::
public static void Unwind<T1,T2>(this Tuple<T1,T2> tuple,out T1 var1,out T2 var2)
{
var1 = tuple.Item1;
var2 = tuple.Item2;
}
Run Code Online (Sandbox Code Playgroud)
但即便如此,因为输出参数根本不是变体.也就是说,如果T1是一个字符串,即使它们是可分配的,我也不能发送一个对象变量,否则就像我可以用手展开一样.我无法提出你可能想要这种差异的原因,但如果它存在,我无法理解为什么你会想要失去它.
任何人都有其他技巧来制作工作元组,或者在C#中更容易像元组一样的元组?
元组的一个重要潜在用途可能是通用的memoization.这在F#这样的语言中很容易,但在C#中却很难.
我目前正在使用Tuples提供一个MethodBase和一个标记数组(常量,对象或参数标记),提供给动态构建的对象以构造某些成员字段.
由于我想在API使用者身上简化语法,因此我创建了可以使用a ConstructorInfo或a MethodInfo和params对象数组的Add方法.
编辑:Eric Lippert像往常一样有很好的动机在这里使用元组,他甚至说我怀疑那里真的没有支持: 元组设计要解决的要求是什么?
我目前正在尝试在handsontable的列上添加搜索过滤器.我可以使用callback搜索插件来隐藏带有css的行但是会中断滚动.搜索插件似乎也只查看表的前100个左右.是否存在可以将行过滤添加到handsontable的插件?
作为一个新奇的东西,我试图看看IL与运行时生成的轻量级代码与VS编译器生成的代码有什么不同,因为我注意到VS代码往往会运行不同的性能配置文件管型.
所以我写了下面的代码::
Func<object,string> vs = x=>(string)x;
Expression<Func<object,string>> exp = x=>(string)x;
var compiled = exp.Compile();
Array.ForEach(vs.Method.GetMethodBody().GetILAsByteArray(),Console.WriteLine);
Array.ForEach(compiled.Method.GetMethodBody().GetILAsByteArray(),Console.WriteLine);
Run Code Online (Sandbox Code Playgroud)
不幸的是,这会引发异常,因为GetMethodBody显然是对表达式树生成的代码的非法操作.我怎样才能以库的方式(即不使用外部工具,除非工具有API)查看代码使用轻量级codegen生成的代码?
编辑:错误发生在第5行,编译.Method.GetMethodBody()抛出异常.
Edit2:有谁知道如何恢复方法中声明的局部变量?或者没有办法GetVariables?
我有一些代码使用MethodInfo在生成的类型上找到的泛型方法.为了避免一些反思,我让代码使用了
ldtoken Method
ldtoken Type
call GetMethodFromHandle(RuntimeMethodHandle,RunTimeTypeHandle)
Run Code Online (Sandbox Code Playgroud)
用于在编译时生成MethodInfos的模式.
但是,如果methodInfo属于泛型类型,并且本身是泛型方法,那么事情就变得棘手了.这里有一些代码只是生成一个GM,它发出了一个开放版本的methodInfo.如果我调用它来检索方法而不是尝试在特定类型上关闭它,我会得到一个令人困惑的异常::
System.Reflection.MethodInfo
GM[M]()不是GenericMethodDefinition.MakeGenericMethod只能在MethodBase.IsGenericMethodDefinition为true的方法上调用.
这是相关代码::
var aBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Test"), AssemblyBuilderAccess.RunAndSave);
var mBuilder = aBuilder.DefineDynamicModule(aBuilder.GetName().Name, true);
var typeBuilder = mBuilder.DefineType("NameSpace.Generic`1",TypeAttributes.AutoClass | TypeAttributes.Sealed | TypeAttributes.Public,typeof(object));
var TypeGenerics = typeBuilder.DefineGenericParameters(new[] { "T" });
var methodBuilder = typeBuilder.DefineMethod("GM", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig);
var methodGenerics = methodBuilder.DefineGenericParameters(new[] { "M" });
methodBuilder.SetSignature(typeof(MethodInfo), null, null, Type.EmptyTypes, null, null);
var ilgenerator = methodBuilder.GetILGenerator();
var typeBuilderClosedOverT = typeBuilder.MakeGenericType(TypeGenerics);
ilgenerator.Emit(OpCodes.Ldtoken, methodBuilder);
ilgenerator.Emit(OpCodes.Ldtoken, typeBuilderClosedOverT);
ilgenerator.Emit(OpCodes.Call,
typeof(MethodBase).GetMethod(
"GetMethodFromHandle",
BindingFlags.Public | …Run Code Online (Sandbox Code Playgroud) 假设我有类似于methodInfo的方法Nullable<int>.HasValue.无论如何将其转换为Nullable<string>.HasValue?
我知道我可以做一个常规的泛型方法,methodInfo.GetGenericMethod()但我没有看到方法为方法中的类型做到这一点,而没有做更多的反射开销.如果我已经有了这个方法,为什么我要再次思考?
有趣的是,这些方法都具有相同的功能MetadataToken,这使得更令人印象深刻的是,不知何故,Module.ResolveMember似乎正在推出正确的方法.
有没有办法做到这一点Module.ResolveMethod?本质上,方法和类型可能都有通用参数,我可能需要切换它们.由于MethodInfo始终表示其令牌相同且令牌表示MethodInfo是该方法的最开放版本.我只需要将它转换为我的类型.
编辑:更多的挖掘,似乎对于类似的东西List<T>.Add,List<int>.Add我检索的元数据令牌实际上存在于我的模块中,而通用定义存在于不同的模块中.
一旦我检索成员,我真的不想做反射,因为很难解决被调用的完全相同的方法.
好吧也许我只是愚蠢,但为什么这不起作用::
var methodinfo = typeof(List<int>).GetMethod("Add");
var handle = methodinfo.MetaDataToken;
var methodinfo2 = methodinfo.Module.ResolveMethod(handle,new []{typeof(string)},null);
Run Code Online (Sandbox Code Playgroud)
为什么methodInfo2说它Add(T)不是Add(string)?
我已经开始使用一些.NET 3.5代码,并发现以下扩展方法用于缓存::
public static TValue GetOrAdd<TKey, TValue>(this Dictionary<TKey, TValue> @this, TKey key,Func<TKey,TValue> factory,bool useLocking)
{
TValue value;
if(!@this.TryGetValue(key,out value))
{
if (useLocking)
{
lock ((@this as ICollection).SyncRoot)
{
if (!@this.TryGetValue(key, out value))
{
@this[key] = value = factory(key);
}
}
}
else
{
@this[key] = value = factory(key);
}
}
return value;
}
Run Code Online (Sandbox Code Playgroud)
有问题的缓存由字符串键和useLocking = true键控.它总是被这种方法访问(没有流浪TryGetValue).使用该SyncRoot属性也没有问题,因为字典是私有的,没有其他地方使用它.双重锁定是危险的,因为字典在写入时不支持读取.虽然技术上没有报告任何问题,因为产品没有发货,我觉得这种方法会导致竞争条件.
切换Dictionary<,>到a Hashtable.我们将失去类型安全性,但我们将能够支持我们追求的并发模型,(1位作者,多位读者).
删除外部TryGetValue.这样每次读取都需要锁定.这可能对性能有害,但获得无争议的锁应该相当便宜.
两者都很糟糕.有人有更好的建议吗?如果这是.NET 4代码,我只需将其切换为a ConcurrentDictionary,但我没有这个选项.
我的印象是在.NET中(非转换)非常便宜且快速.但是,这似乎不是阵列的情况.我想在这里做一个非常简单的演员,拿一个T1 []并演员为T2 [].其中T1:T2.
有3种方法可以做到这一点,我称之为以下::
DropCasting: T2[] array2 = array;
CastClass: (T2[])array;
IsInst: array as T2[];
Run Code Online (Sandbox Code Playgroud)
我创建了这样做的方法,不幸的是,C#似乎创建了一些相当奇怪的代码,具体取决于它是否是通用的.(如果它的通用DropCasting使用了castclass运算符.在两种情况下,当T1:T2时拒绝发出'as'运算符.
无论如何,我写了一些动态方法,我测试了一些令人惊讶的结果(string [] => object []):
DropCast : 223ms
IsInst : 3648ms
CastClass: 3732ms
Run Code Online (Sandbox Code Playgroud)
垂直投射比任何一个投射操作员快约18倍.为什么阵列的播放速度如此之慢?对于像string => object这样的普通对象,差异要小得多.
DropCast : 386ms
IsInst : 611ms
CastClass: 519ms
Run Code Online (Sandbox Code Playgroud)
基准代码如下:
class Program
{
static readonly String[] strings = Enumerable.Range(0, 10).Select(x => x.ToString()).ToArray();
static Func<string[], object[]> Dropcast = new Func<Func<string[], object[]>>(
() =>
{
var method = new DynamicMethod("DropCast", typeof(object[]), new[] { typeof(object), typeof(string[]) },true);
var ilgen = method.GetILGenerator();
ilgen.Emit(OpCodes.Ldarg_1); …Run Code Online (Sandbox Code Playgroud) c# ×9
arrays ×2
.net ×1
cil ×1
concurrency ×1
f# ×1
handsontable ×1
il ×1
inline ×1
reflection ×1
tuples ×1