Автовивификация в C # - PullRequest
       13

Автовивификация в C #

0 голосов
/ 21 декабря 2010

Пытаясь обернуть голову вокруг автовивификации perl и основываясь на том, на что это похоже, похоже, что она работает аналогично динамике в C #, так как динамическому объекту не назначается тип до времени выполнения или я полностью отключен. Если это так, то Есть ли в C # идея, с которой я мог бы справиться, что имеет смысл?

Редактировать
Хорошо, так что я, видимо, далеко. Итак, как вторая часть вопроса из 2 частей, есть ли что-нибудь концептуально сопоставимое в C #? Чтобы было ясно, я ищу концепцию в C #, которая сравнима с автовивификацией. Не должно быть точно таким же, но достаточно близко концептуально, чтобы иметь смысл. И, как я сказал ранее, я ни в коем случае не хакер perl или python, но я знаком с основанными на c языками C, C ++, C #, java, javascript. Я думал о динамике C #, но сейчас я думаю о ленивой загрузке, основанной на информации здесь, если это поможет ....

Ответы [ 6 ]

9 голосов
/ 21 декабря 2010

Я не могу говорить с 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}
}
4 голосов
/ 21 декабря 2010

Учебное пособие по Ури Гутмана может быть полезным.

По сути, это способность до сих пор нетронутых агрегатов и членов агрегатов оживать при первом использовании.

Например, я могу сделать это:

#!/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 становится ссылкой на массив, потому что она разыменовывается как таковая.

3 голосов
/ 21 декабря 2010

Вы можете реализовать поведение, подобное автовификации, с помощью, скажем, IDictionary<X,Y>, который возвращает (и сохраняет) новый IDictionary<X,Y> (например, рекурсивно того же типа), когда [] для неустановленного ключа происходит. Этот подход используется в Ruby с большим успехом ( пример ) - однако, на самом деле он не так полезен в статически типизированном языке, потому что нет способа "чисто" добраться до значений листа - при минимум в контексте большинства существующих контрактов, таких как IDictionary.

С появлением dynamic, это может быть возможно в C # делать разумно, но я не знаю.

1 голос
/ 16 мая 2011

Как насчет чего-то подобного для простой реализации авививификации, например поведения словаря в 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);
                }
            }

        }
    }
}
0 голосов
/ 23 октября 2017

Используя индексаторы и динамическую динамику 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"]);
0 голосов
/ 10 декабря 2016

Я бы рекомендовал использовать методы расширения вместо наследования.

например:

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;
        }
    }
}
...