Esegue un’operazione contro ogni elemento in una collezione di oggetti di input.
Sintassi
ForEach-Object <ScriptBlock> >]
ForEach-Object <String> >]
ForEach-Object -Parallel <scriptblock>
Descrizione
La ForEach-Object
cmdlet esegue un’operazione su ogni elemento di una collezione di oggetti di input. Gli oggetti di input possono essere inviati alla cmdlet o specificati usando il parametro InputObject.
A partire da Windows PowerShell 3.0, ci sono due modi diversi per costruire un ForEach-Object
comando.
-
Blocco di script. Puoi usare un blocco di script per specificare l’operazione. All’interno del blocco di script, usate la variabile
$_
per rappresentare l’oggetto corrente. Il blocco di script è il valore del parametroProcess. Il blocco di script può contenere qualsiasi script PowerShell.Per esempio, il seguente comando ottiene il valore della proprietà ProcessName di ogni processo sul computer.
Get-Process | ForEach-Object {$_.ProcessName}
ForEach-Object
supporta ilbegin
process
, eend
blocchi come descritto inabout_functions.Nota
I blocchi di script vengono eseguiti nell’ambito del chiamante. Perciò i blocchi hanno accesso alle variabili in quell’ambito e possono creare nuove variabili che persistono in quell’ambito dopo il completamento del cmdlet.
-
Dichiarazione di operazione. Puoi anche scrivere un’istruzione di operazione, che è molto più simile al linguaggio naturale. Puoi usare l’istruzione di operazione per specificare il valore di una proprietà o chiamare un metodo. Le istruzioni di operazione sono state introdotte in Windows PowerShell 3.0.
Per esempio, il seguente comando ottiene anche il valore della proprietà ProcessName di ogni processo sul computer.
Get-Process | ForEach-Object ProcessName
-
Blocco script a esecuzione parallela. A partire da PowerShell 7.0, è disponibile un terzo set di parametri che esegue ogni blocco di script in parallelo. Il parametro ThrottleLimit limita il numero di script in esecuzione parallela alla volta. Come prima, usa la variabile
$_
per rappresentare l’oggetto di input corrente nel blocco di script. Usare la parola chiave$using:
per passare i riferimenti variabili allo script in esecuzione.In PowerShell 7, viene creato un nuovo runspace per ogni iterazione del ciclo per garantire il massimo isolamento.Questo può essere un grande impatto sulle prestazioni e sulle risorse se il lavoro che si sta facendo è piccolo rispetto alla creazione di nuovi runspace o se ci sono molte iterazioni che eseguono un lavoro significativo. A partire da PowerShell 7.1, i runspace di un pool di runspace vengono riutilizzati per default. La dimensione del pool di runspace è specificata dal parametro ThrottleLimit. La dimensione predefinita del pool di runspace è 5. Puoi comunque creare un nuovo runspace per ogni iterazione usando l’interruttore UseNewRunspace.
Per impostazione predefinita, i blocchi di script paralleli utilizzano la directory di lavoro corrente del chiamante che ha avviato i task paralleli.
Gli errori che non terminano vengono scritti nel flusso di errore del cmdlet quando si verificano nei blocchi di script in esecuzione parallela. Poiché l’ordine di esecuzione dei blocchi di script in parallelo non può essere determinato, l’ordine in cui gli errori appaiono nel flusso degli errori è casuale. Allo stesso modo, i messaggi scritti in altri flussi di dati, come warning, verbose, o information sono scritti in quei flussi di dati in un ordine indeterminato.
Gli errori terminanti, come le eccezioni, terminano la singola istanza parallela degli scriptblock in cui si verificano. Un errore di terminazione in uno scriptblock può non causare la terminazione del
Foreach-Object
cmdlet. Gli altri blocchi di script, eseguiti in parallelo, continuano a funzionare a meno che non incontrino anch’essi un errore di terminazione. L’errore terminante viene scritto nel flusso di dati degli errori come un ErrorRecord con un FullyQualifiedErrorId diPSTaskException
. Gli errori terminanti possono essere convertiti in errori non terminanti usando PowerShell try/catch o trapblocks.
Esempi
Esempio 1: Dividere interi in un array
Questo esempio prende un array di tre interi e divide ciascuno di essi per 1024.
30000, 56798, 12432 | ForEach-Object -Process {$_/1024}29.29687555.46679687512.140625
Esempio 2: Ottenere la lunghezza di tutti i file in una directory
Questo esempio processa i file e le directory nella directory di installazione di PowerShell $PSHOME
.
Get-ChildItem $PSHOME | ForEach-Object -Process {if (!$_.PSIsContainer) {$_.Name; $_.Length / 1024; " " }}
Se l’oggetto non è una directory, il blocco di script ottiene il nome del file, divide il valore della sua proprietà Length per 1024, e aggiunge uno spazio (” “) per separarlo dalla voce successiva. Il comando usa la proprietà PSISContainer per determinare se un oggetto è una directory.
Esempio 3: Operare sugli eventi di sistema più recenti
Questo esempio scrive i 1000 eventi più recenti dal registro eventi di sistema in un file di testo. L’ora corrente viene visualizzata prima e dopo l’elaborazione degli eventi.
$Events = Get-EventLog -LogName System -Newest 1000$events | ForEach-Object -Begin {Get-Date} -Process {Out-File -FilePath Events.txt -Append -InputObject $_.Message} -End {Get-Date}
Get-EventLog
ottiene i 1000 eventi più recenti dal registro eventi di sistema e li memorizza nella variabile$Events
$Events
viene poi convogliato al ForEach-Object
cmdlet. Il parametro Begin mostra la data e l’ora attuali. Successivamente, il parametro Process utilizza il cmdlet Out-File
per creare un file di testo chiamato events.txt e memorizza la proprietà del messaggio di ogni evento in questo file. Infine, il parametro End è usato per visualizzare la data e l’ora dopo che tutta l’elaborazione è stata completata.
Esempio 4: Cambiare il valore di una chiave di registro
Questo esempio cambia il valore della voce di registro RemotePath in tutte le sottochiavi sotto la chiaveHKCU:\Network
in testo maiuscolo.
Get-ItemProperty -Path HKCU:\Network\* | ForEach-Object {Set-ItemProperty -Path $_.PSPath -Name RemotePath -Value $_.RemotePath.ToUpper();}
Puoi usare questo formato per cambiare la forma o il contenuto del valore di una voce di registro.
Ogni sottochiave nella chiave Network rappresenta un’unità di rete mappata che si ricollega all’accesso. La voceRemotePath contiene il percorso UNC dell’unità collegata. Per esempio, se si mappa l’unità E:a \\Server\Share
, una sottochiave E viene creata in HKCU:\Network
con il valore RemotePathregistry impostato a \\Server\Share
.
Il comando usa la cmdlet Get-ItemProperty
per ottenere tutte le sottochiavi della chiave Network e la cmdlet Set-ItemProperty
per cambiare il valore della voce di registro RemotePath in ogni chiave.Nel comando Set-ItemProperty
, il percorso è il valore della proprietà PSPath della chiave di registro. Questa è una proprietà dell’oggetto Microsoft .NET Framework che rappresenta la chiave di registro, non la voce di registro. Il comando usa il metodo ToUpper() del valore RemotePath, che è una stringa (REG_SZ).
Perché Set-ItemProperty
sta cambiando la proprietà di ogni chiave, il ForEach-Object
cmdlet è necessario per accedere alla proprietà.
Esempio 5: Utilizzare la variabile automatica $Null
Questo esempio mostra l’effetto del piping della variabile automatica $Null
alla ForEach-Object
cmdlet.
1, 2, $null, 4 | ForEach-Object {"Hello"}HelloHelloHelloHello
Perché PowerShell tratta null come un segnaposto esplicito, la ForEach-Object
cmdlet genera un valore per $Null
, proprio come fa per altri oggetti che le vengono inviati tramite pipe.
Esempio 6: Ottenere i valori delle proprietà
Questo esempio ottiene il valore della proprietà Path di tutti i moduli PowerShell installati utilizzando il parametro MemberName del ForEach-Object
cmdlet.
Get-Module -ListAvailable | ForEach-Object -MemberName PathGet-Module -ListAvailable | Foreach Path
Il secondo comando è equivalente al primo. Usa l’alias Foreach
dell’ForEach-Object
cmdlet e omette il nome del parametro MemberName, che è opzionale.
Il cmdlet ForEach-Object
è utile per ottenere valori di proprietà, perché ottiene il valore senza cambiarne il tipo, a differenza dei cmdlet Format o del cmdlet Select-Object
, che cambiano il tipo di valore della proprietà.
Esempio 7: dividere i nomi dei moduli in nomi di componenti
Questo esempio mostra tre modi per dividere due nomi di moduli separati da punti nei loro nomi di componenti.
"Microsoft.PowerShell.Core", "Microsoft.PowerShell.Host" | ForEach-Object {$_.Split(".")}"Microsoft.PowerShell.Core", "Microsoft.PowerShell.Host" | ForEach-Object -MemberName Split -ArgumentList ".""Microsoft.PowerShell.Core", "Microsoft.PowerShell.Host" | Foreach Split "."MicrosoftPowerShellCoreMicrosoftPowerShellHost
I comandi chiamano il metodo Split delle stringhe. I tre comandi usano una sintassi diversa, ma sono equivalenti e intercambiabili.
Il primo comando usa la sintassi tradizionale, che include un blocco di script e l’operatore dell’oggetto corrente $_
. Usa la sintassi dei punti per specificare il metodo e le parentesi per racchiudere il delimitatore dell’argomento.
Il secondo comando usa il parametro MemberName per specificare il metodo Split e il parametroArgumentName per identificare il punto (“.”) come delimitatore di divisione.
Il terzo comando usa l’alias Foreach della cmdlet ForEach-Object
e omette i nomi dei parametri MemberName e ArgumentList, che sono opzionali.
Esempio 8: usare ForEach-Object con due blocchi di script
In questo esempio, passiamo due blocchi di script in posizione. Tutti i blocchi di script si legano al parametroProcess. Tuttavia, sono trattati come se fossero stati passati ai parametri Begin eProcess.
1..2 | ForEach-Object { 'begin' } { 'process' }beginprocessprocess
Esempio 9: usare ForEach-Object con più di due blocchi di script
In questo esempio, passiamo due blocchi di script in posizione. Tutti i blocchi di script si legano al parametroProcess. Tuttavia, sono trattati come se fossero stati passati ai parametri Begin, Process e End.
1..2 | ForEach-Object { 'begin' } { 'process A' } { 'process B' } { 'end' }beginprocess Aprocess Bprocess Aprocess Bend
Nota
Il primo blocco di script è sempre mappato al blocco begin
, l’ultimo blocco è mappato sul blocco end
, e i blocchi in mezzo sono tutti mappati sul blocco process
.
Esempio 10: Eseguire più blocchi di script per ogni elemento della pipeline
Come mostrato nell’esempio precedente, più blocchi di script passati usando il parametro Process vengono mappati ai parametri Begin e End. Per evitare questa mappatura, è necessario fornire valori espliciti per i parametri Begin e End.
1..2 | ForEach-Object -Begin $null -Process { 'one' }, { 'two' }, { 'three' } -End $nullonetwothreeonetwothree
Esempio 11: Eseguire uno script lento in lotti paralleli
Questo esempio esegue un semplice blocco di script che valuta una stringa e dorme per un secondo.
$Message = "Output:"1..8 | ForEach-Object -Parallel { "$using:Message $_" Start-Sleep 1} -ThrottleLimit 4Output: 1Output: 2Output: 3Output: 4Output: 5Output: 6Output: 7Output: 8
Il valore del parametro ThrottleLimit è impostato a 4 in modo che l’input sia processato in lotti di quattro.La parola chiave $using:
è usata per passare la variabile $Message
in ogni blocco di script parallelo.
Esempio 12: Recupera voci di log in parallelo
Questo esempio recupera 50.000 voci di log da 5 log di sistema su una macchina Windows locale.
$logNames = 'Security','Application','System','Windows PowerShell','Microsoft-Windows-Store/Operational'$logEntries = $logNames | ForEach-Object -Parallel { Get-WinEvent -LogName $_ -MaxEvents 10000} -ThrottleLimit 5$logEntries.Count50000
Il parametro Parallel specifica il blocco di script che viene eseguito in parallelo per ogni nome di log in ingresso. Il parametro ThrottleLimit assicura che tutti e cinque i blocchi di script vengano eseguiti allo stesso tempo.
Esempio 13: Eseguire in parallelo come un lavoro
Questo esempio esegue un semplice blocco di script in parallelo, creando due lavori in background alla volta.
$job = 1..10 | ForEach-Object -Parallel { "Output: $_" Start-Sleep 1} -ThrottleLimit 2 -AsJob$job | Receive-Job -WaitOutput: 1Output: 2Output: 3Output: 4Output: 5Output: 6Output: 7Output: 8Output: 9Output: 10
la variabile $job
riceve l’oggetto job che raccoglie i dati di output e controlla lo stato di esecuzione. E questo invia l’output alla console, proprio come se ForEach-Object -Parallel
fosse stato eseguito senza AsJob.
Esempio 14: Usare riferimenti variabili sicuri per il thread
Questo esempio invoca blocchi di script in parallelo per raccogliere oggetti Process dal nome unico.
$threadSafeDictionary = ]::new()Get-Process | ForEach-Object -Parallel { $dict = $using:threadSafeDictionary $dict.TryAdd($_.ProcessName, $_)}$threadSafeDictionaryNPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName ------ ----- ----- ------ -- -- ----------- 82 82.87 130.85 15.55 2808 2 pwsh
Una singola istanza di un oggetto ConcurrentDictionary viene passata ad ogni blocco di script per raccogliere gli oggetti. Poiché il ConcurrentDictionary è thread safe, è sicuro di essere modificato da ogni script parallelo. Un oggetto non thread-safe, come System.Collections.Generic.Dictionary, non sarebbe sicuro da usare qui.
Note
Questo esempio è un uso molto inefficiente del parametro Parallel. Lo script aggiunge semplicemente l’oggetto inputobject ad un oggetto dizionario concorrente. È banale e non vale l’overhead di invocare ogni script in un thread separato. Eseguire ForEach-Object
normalmente senza il Parallelswitch è molto più efficiente e veloce. Questo esempio ha il solo scopo di dimostrare come utilizzare le variabili sicure per il thread.
Esempio 15: Scrivere errori con l’esecuzione parallela
Questo esempio scrive nel flusso degli errori in parallelo, dove l’ordine degli errori scritti è casuale.
1..3 | ForEach-Object -Parallel { Write-Error "Error: $_"}Write-Error: Error: 1Write-Error: Error: 3Write-Error: Error: 2
Esempio 16: Terminare gli errori in esecuzione parallela
Questo esempio dimostra un errore di terminazione in uno scriptblock in esecuzione parallela.
1..5 | ForEach-Object -Parallel { if ($_ -eq 3) { throw "Terminating Error: $_" } Write-Output "Output: $_"}Exception: Terminating Error: 3Output: 1Output: 4Output: 2Output: 5
Output: 3
non viene mai scritto perché lo scriptblock parallelo per quella iterazione è terminato.
Esempio 17: Passare variabili in scriptBlockSet paralleli annidati
Si può creare una variabile al di fuori di un Foreach-Object -Parallel
blocco di script con scope e usarla all’interno del blocco di script con la parola chiave $using
.
$test1 = 'TestA'1..2 | Foreach-Object -Parallel { $using:test1}TestATestA# You CANNOT create a variable inside a scoped scriptblock# to be used in a nested foreach parallel scriptblock.$test1 = 'TestA'1..2 | Foreach-Object -Parallel { $using:test1 $test2 = 'TestB' 1..2 | Foreach-Object -Parallel { $using:test2 }}Line | 2 | 1..2 | Foreach-Object -Parallel { | ~~~~~~~~~~~~~~~~~~~~~~~~~~ | The value of the using variable '$using:test2' cannot be retrieved because it has not been set in the local session.
Lo scriptblock annidato non può accedere alla variabile $test2
e viene lanciato un errore.
Parametri
Specifica un array di argomenti per una chiamata di metodo. Per maggiori informazioni sul comportamento diArgumentList, vedi about_Splatting.
Questo parametro è stato introdotto in Windows PowerShell 3.0.
Type: | Object |
Alias: | Args |
Posizione: | Nominato |
Valore predefinito: | Nessuno |
Accetta input della pipeline: | Falso |
Accetta caratteri jolly: | Falso |
Fa sì che l’invocazione parallela venga eseguita come un lavoro PowerShell. Un singolo oggetto lavoro viene restituito al posto dell’output dei blocchi di script in esecuzione. L’oggetto job contiene lavori figli per ogni blocco di script parallelo che viene eseguito. L’oggetto job può essere usato da tutti i cmdlets PowerShell job, per monitorare lo stato di esecuzione e recuperare i dati.
Questo parametro è stato introdotto in PowerShell 7.0.
Type: | SwitchParameter |
Posizione: | Nominato |
Valore predefinito: | Nessuno |
Accetta input della pipeline: | Falso |
Accetta caratteri jolly: | Falso |
Specifica un blocco di script che viene eseguito prima che questa cmdlet elabori qualsiasi oggetto di input. Questo blocco di script viene eseguito solo una volta per l’intera pipeline. Per maggiori informazioni sul blocco begin
, vedereabout_Functions.
Tipo: | ScriptBlock |
Posizione: | Nominato |
Valore predefinito: | Nessuno |
Accetta input della pipeline: | Falso |
Accetta caratteri jolly: | Falso |
Vi chiede conferma prima di eseguire la cmdlet.
Tipo: | SwitchParameter |
Alias: | cf |
Posizione: | Nominato |
Valore predefinito: | Falso |
Accetta input della pipeline: | Falso |
Accetta caratteri jolly: | Falso |
Specifica un blocco di script che viene eseguito dopo che questa cmdlet processa tutti gli oggetti di input. Questo blocco di script viene eseguito solo una volta per l’intera pipeline. Per maggiori informazioni sul blocco end
, vedereabout_Functions.
Tipo: | ScriptBlock |
Posizione: | Nominato |
Valore predefinito: | Nessuno |
Accetta input della pipeline: | Falso |
Accetta caratteri jolly: | Falso |
Specifica gli oggetti input. ForEach-Object
esegue il blocco di script o l’istruzione di operazione su ogni oggetto di input. Inserisci una variabile che contiene gli oggetti, o digita un comando o un’espressione che ottiene gli oggetti.
Quando usi il parametro InputObject con ForEach-Object
, invece di convogliare i risultati del comando in ForEach-Object
, il valore InputObject è trattato come un singolo oggetto. Questo è vero anche se il valore è una collezione che è il risultato di un comando, come -InputObject (Get-Process)
.Poiché InputObject non può restituire proprietà individuali da un array o da una collezione di oggetti, si raccomanda che se si usa ForEach-Object
per eseguire operazioni su una collezione di oggetti per quegli oggetti che hanno valori specifici in proprietà definite, si usi ForEach-Object
nella pipeline, come mostrato negli esempi di questo argomento.
Type: | PSObject | |
Posizione: | Nominato | |
Valore predefinito: | Nessuno | |
Accetta input della pipeline: | Vero | |
Accetta caratteri jolly: | Falso |
Specifica la proprietà da ottenere o il metodo da chiamare.
I caratteri jolly sono permessi, ma funzionano solo se la stringa risultante si risolve in un valore unico.Per esempio, se si esegue Get-Process | ForEach -MemberName *Name
, il pattern jolly corrisponde a più di un membro causando il fallimento del comando.
Questo parametro è stato introdotto in Windows PowerShell 3.0.
Tipo: | Stringa | |
Posizione: | 0 | |
Valore predefinito: | Nessuno | |
Accetta input pipeline: | Falso | |
Accetta caratteri jolly: | Vero |
Specifica il blocco di script da utilizzare per l’elaborazione parallela degli oggetti di input. Inserisci un blocco di script che descrive l’operazione.
Questo parametro è stato introdotto in PowerShell 7.0.
Type: | ScriptBlock |
Posizione: | Nominato |
Valore predefinito: | Nessuno |
Accetta input della pipeline: | Falso |
Accetta caratteri jolly: | Falso |
Specifica l’operazione che viene eseguita su ogni oggetto di input. Questo blocco di script viene eseguito per ogni oggetto nella pipeline. Per maggiori informazioni sul blocco process
, si vedaabout_Functions.
Quando si forniscono più blocchi di script al parametro Process, il primo blocco di script viene sempre mappato al blocco begin
. Se ci sono solo due blocchi di script, il secondo blocco è mappato al blocco process
. Se ci sono tre o più blocchi di script, il primo blocco di script è sempre mappato al blocco begin
, l’ultimo blocco è mappato al blocco end
, e i blocchi in mezzo sono tutti mappati al blocco process
.
Type: | ScriptBlock |
Posizione: | 0 |
Valore predefinito: | Nessuno |
Accetta input della pipeline: | Falso |
Accetta caratteri jolly: | Falso |
Specifica tutti i blocchi di script che non sono presi dal parametro Process.
Questo parametro è stato introdotto in Windows PowerShell 3.0.
Type: | ScriptBlock |
Posizione: | Nominato |
Valore predefinito: | Nessuno |
Accetta input della pipeline: | Falso |
Accetta caratteri jolly: | Falso |
Specifica il numero di blocchi di script che in parallelo. Gli oggetti di input sono bloccati fino a quando il numero di blocchi di script in esecuzione non scende sotto il ThrottleLimit. Il valore predefinito è 5
.
Questo parametro è stato introdotto in PowerShell 7.0.
Tipo: | Int32 |
Posizione: | Nominato |
Valore predefinito: | 5 |
Accetta input della pipeline: | Falso |
Accetta caratteri jolly: | Falso |
Specifica il numero di secondi di attesa per tutti gli input da elaborare in parallelo. Dopo il tempo di timeout specificato, tutti gli script in esecuzione vengono fermati. E qualsiasi oggetto di input rimanente da elaborare viene ignorato. Il valore predefinito di 0
disabilita il timeout, e ForEach-Object -Parallel
può essere eseguito indefinitamente. Digitando Ctrl+C sulla linea di comando si ferma un comandoForEach-Object -Parallel
in esecuzione. Questo parametro non può essere usato insieme al parametro AsJob.
Questo parametro è stato introdotto in PowerShell 7.0.
Tipo: | Int32 |
Posizione: | Nominato |
Valore predefinito: | 0 |
Accetta ingresso pipeline: | Falso |
Accetta caratteri jolly: | Falso |
Mostra cosa accadrebbe se il cmdlet venisse eseguito. Il cmdlet non viene eseguito.
Type: | SwitchParameter |
Alias: | wi |
Posizione: | Nominato |
Valore predefinito: | Falso |
Accetta input della pipeline: | Falso |
Accetta caratteri jolly: | Falso |
Inputs
PSObject
Puoi inviare qualsiasi oggetto a questa cmdlet.
Outputs
PSObject
Questa cmdlet restituisce oggetti che sono determinati dall’input.
Note
-
La
ForEach-Object
cmdlet funziona come l’istruzione Foreach, tranne per il fatto che non è possibile inserire input in una istruzione Foreach. Per maggiori informazioni sull’istruzione Foreach, vediabout_Foreach. -
A partire da PowerShell 4.0, sono stati aggiunti i metodi
Where
eForEach
da usare con le collezioni. Puoi leggere di più su questi nuovi metodi qui about_arrays -
Il set di parametri
ForEach-Object -Parallel
usa l’API interna di PowerShell per eseguire ogni blocco di script. Questo è significativamente più costoso che eseguireForEach-Object
normalmente con sequentialprocessing. È importante usare Parallel dove l’overhead dell’esecuzione in parallelo è piccolo rispetto al lavoro che il blocco di script esegue. Per esempio:- Script intensivi di calcolo su macchine multi-core
- Scripts che passano il tempo ad aspettare i risultati o a fare operazioni su file
L’utilizzo del parametro Parallel può causare un’esecuzione degli script molto più lenta del normale. Specialmente se gli script paralleli sono banali. Sperimenta con Parallel per scoprire dove può essere utile.
Importante
Il parametro
ForEach-Object -Parallel
imposta i blocchi di script in parallelo su processthreads separati. La parola chiave$using:
permette di passare i riferimenti alle variabili dal thread di invocazione della cmdlet a ciascun thread del blocco di script in esecuzione. Poiché i blocchi di script vengono eseguiti in thread diversi, le variabili oggetto passate per riferimento devono essere utilizzate in modo sicuro. Generalmente è sicuro leggere da oggetti referenziati che non cambiano. Ma se lo stato dell’oggetto viene modificato, allora è necessario utilizzare oggetti sicuri per il thread, come i tipi .Net System.Collection.Concurrent (vedi esempio 11).
- Compare-Object
- Where-Object
- Group-Object
- Measure-Object
- New-Object
- Select-Object
- Sort-Object
- Tee-Object