试图围绕 perl 的 Autovivification 并根据它的声音,它似乎与 C# 中的动态类似,因为动态对象直到运行时才被分配类型,或者,我完全不在这里。如果是这样,那么是否有一个类似的想法可以让我在 C# 中摆脱这种想法?
编辑
好吧,我显然已经走了。因此,作为 2 部分问题的第二部分,C# 中有什么概念上可比的吗?需要明确的是,我正在寻找与 Autovivification 相媲美的 C# 概念。不必完全相同,但在概念上足够接近才有意义。正如我之前所说的那样,无论如何我都不是 perl 黑客或 python 黑客,但是,我熟悉基于 c 的语言 C、C++、C#、java、javascript。我在考虑 C# 的动态,但是,到目前为止,我正在考虑基于这里的信息进行延迟加载,如果这有帮助的话....
6 回答
我不会说 C#,但用外行的话来说,Perl 的自动生存是在需要时从未定义的值创建容器对象的过程。
尽管 Perl 的大部分内容都是动态的,但 Perl 的解引用语法在编译时明确指定了引用的类型。这允许解释器在定义变量之前知道它需要从变量中得到什么。
my $var; # undefined
# to autovivify to an array:
@$var = 1..5; # @ here implies ARRAY
$$var[4] = 5; # square brackets imply ARRAY
$#$var; # $# implies ARRAY (returns the last index number)
# to autovivify to a hash:
%$var = (a => 1); # % implies HASH
$$var{asdf} = 5; # curly braces imply HASH
这个列表可能更长,但应该给你一个想法。
所以基本上,当你有这样的一行时:
my $var;
$var->[1]{x}[3]{asdf}
Perl 查看 右侧的->
并看到方括号。这意味着调用者$var
必须是数组引用。由于调用者未定义,Perl 创建一个新数组并将其引用安装到$var
. 然后对每个后续的取消引用重复相同的过程。
所以上面的那行真的意味着:
(((($var //= [])->[1] //= {})->{x} //= [])->[3] //= {})->{asdf} ;
这是相当可怕的,因此会自动存活。(//=
是 perl 5.10+ 中的定义或赋值运算符)
更新:
根据 cjm 的评论,要将其放入一般的非 perl 术语中,以实现另一种语言的自动生存,您需要一个支持通过[...]
和索引的惰性对象{...}
。当执行这些索引操作中的任何一个时,对象将自己替换为数组或散列。每次访问对象时,如果单元格为空,它应该返回另一个惰性对象。
obj = new lazy_obj()
level1 = obj[4] # sets obj to be an array, returns a new lazy_obj for level1
level2 = level1{asdf} # sets level1 (and obj[4]) to a hash,
# returns a new lazy_obj for level2
所以基本上你需要两件事,创建一个支持使用数组和哈希下标(或等效)索引的对象的能力,以及一种机制,使得一个对象可以用另一个对象替换内存中的自己(或者可以将自己锁定到一种解释,然后在内部存储新对象。
下面的伪代码可能是一个开始:
class autoviv {
private var content;
method array_subscript (idx) {
if (!content) {
content = new Array();
}
if (typeof content == Array) {
if (exists content[idx]) return content[idx];
return content[idx] = new autoviv();
} else {
throw error
}
}
method hash_subscript (idx) {
if (!content) {
content = new Hash();
}
if (typeof content == Hash) {
if (exists content{idx}) return content{idx};
return content{idx} = new autoviv();
} else {
throw error
}
}
// overload all other access to return undefined, so that the value
// still looks empty for code like:
//
// var auto = new autoviv();
// if (typeof auto[4] == autoviv) {should run}
// if (auto[4]) {should not run}
}
Uri Guttman 的自动复活教程可能会有一些用处。
基本上,它是迄今为止未触及的聚合体和聚合体的成员在第一次使用时立即恢复活力的能力。
例如,我可以这样做:
#!/usr/bin/perl
use strict; use warnings;
use Data::Dumper;
my @dummy;
push @{ $dummy[0] }, split ' ', 'this that and the other';
push @{ $dummy[1] }, { qw(a b c d) };
print Dumper \@dummy;
在它们被取消引用之前既不$dummy[0]
也不存在。$dummy[1]
现在,如果您愿意放弃strict
(您不应该放弃),您还可以执行以下操作:
use Data::Dumper;
@$x = qw(a b c d);
print Dumper $x;
从而未定义的变量$x
成为数组引用,因为它被取消引用。
您可以通过创建say 来实现类似自动验证的行为,当出现未设置的键时IDictionary<X,Y>
返回(并存储)一个新的IDictionary<X,Y>
(例如递归相同的类型) 。[]
这种方法在 Ruby 中取得了巨大的成功(一个例子)——然而,它在静态类型语言中真的没那么有用,因为没有办法干净地“获取”叶值——至少在大多数现有的上下文中合同,例如IDictionary
.
随着 的出现dynamic
,这可能在 C# 中可以正常执行,但我不知道。
对于像 C# 中的字典行为这样的自动激活的简单实现,这样的事情怎么样?显然,这并没有以 Perl 的通用方式处理它,但我相信它具有相同的效果。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
// The purpose of this class is to provide a dictionary with auto-vivification behaviour similar to Perl's
// Using dict[index] will succeed regardless of whether index exists in the dictionary or not.
// A default value can be set to be used as an initial value when the key doesn't exist in the dictionary
namespace XMLTest
{
class AutoDictionary<TKey,TValue> : Dictionary<TKey,TValue> {
Object DefaultValue ;
public AutoDictionary(Object DefaultValue) {
this.DefaultValue = DefaultValue;
}
public AutoDictionary() {
this.DefaultValue = null;
}
public new TValue this[TKey index] {
get {
try {
return base[index];
}
catch (KeyNotFoundException) {
base.Add(index, (TValue)DefaultValue);
return (TValue)DefaultValue ;
}
}
set {
try {
base[index] = value ;
}
catch (KeyNotFoundException) {
base.Add(index, value);
}
}
}
}
}
我建议使用扩展方法而不是继承。
例如:
namespace DictionaryEx
{
public static class Ex
{
public static TV Vivify<TK, TV>(this IDictionary<TK, TV> dict, TK key)
{
var value = default(TV);
if (dict.TryGetValue(key, out value))
{
return value;
}
value = default(TV);
dict[key] = value;
return value;
}
public static TV Vivify<TK, TV>(this IDictionary<TK, TV> dict, TK key, TV defaultValue)
{
TV value;
if (dict.TryGetValue(key, out value))
{
return value;
}
dict[key] = defaultValue;
return defaultValue;
}
public static TV Vivify<TK, TV>(this IDictionary<TK, TV> dict, TK key, Func<TV> valueFactory)
{
TV value;
if (dict.TryGetValue(key, out value))
{
return value;
}
value = valueFactory();
dict[key] = value;
return value;
}
}
}
使用索引器和 C# 4.0 动态,
class Tree
{
private IDictionary<string, object> dict = new Dictionary<string, object>();
public dynamic this[string key]
{
get { return dict.ContainsKey(key) ? dict[key] : dict[key] = new Tree(); }
set { dict[key] = value; }
}
}
// Test:
var t = new Tree();
t["first"]["second"]["third"] = "text";
Console.WriteLine(t["first"]["second"]["third"]);
DynamicObject 也可用于实现不同的语法,
using System;
using System.Collections.Generic;
using System.Dynamic;
class Tree : DynamicObject
{
private IDictionary<object, object> dict = new Dictionary<object, object>();
// for t.first.second.third syntax
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var key = binder.Name;
if (dict.ContainsKey(key))
result = dict[key];
else
dict[key] = result = new Tree();
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
dict[binder.Name] = value;
return true;
}
// for t["first"]["second"]["third"] syntax
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
{
var key = indexes[0];
if (dict.ContainsKey(key))
result = dict[key];
else
dict[key] = result = new Tree();
return true;
}
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
{
dict[indexes[0]] = value;
return true;
}
}
// Test:
dynamic t = new Tree();
t.first.second.third = "text";
Console.WriteLine(t.first.second.third);
// or,
dynamic t = new Tree();
t["first"]["second"]["third"] = "text";
Console.WriteLine(t["first"]["second"]["third"]);